diff --git a/.gitignore b/.gitignore index 8392b92c1a..f465ad7306 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ tmp/ .#* *~ *.~lock.* +tags .emacs.desktop .DS_Store *.sublime-project* @@ -36,7 +37,4 @@ config/initializers/feature_toggle.rb NERD_tree* coverage libpeerconnection.log -tags - -# Ignore application configuration /config/application.yml diff --git a/Gemfile b/Gemfile index 3f84c28183..521dbaeab1 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,7 @@ gem 'gmaps4rails' gem 'spinjs-rails' gem 'rack-ssl', :require => 'rack/ssl' gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg' +gem 'angularjs-file-upload-rails', '~> 1.1.0' gem 'figaro' gem 'foreigner' diff --git a/Gemfile.lock b/Gemfile.lock index 94c6ead27a..2eab9c7f59 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,6 +153,7 @@ GEM railties (>= 3.1) sprockets tilt + angularjs-file-upload-rails (1.1.0) angularjs-rails (1.2.13) ansi (1.4.2) arel (3.0.3) @@ -508,6 +509,7 @@ DEPENDENCIES active_model_serializers andand angular-rails-templates + angularjs-file-upload-rails (~> 1.1.0) angularjs-rails awesome_print aws-sdk diff --git a/app/assets/javascripts/admin/admin.js.coffee b/app/assets/javascripts/admin/admin.js.coffee index c80e6252de..cbb824f97d 100644 --- a/app/assets/javascripts/admin/admin.js.coffee +++ b/app/assets/javascripts/admin/admin.js.coffee @@ -1,3 +1,3 @@ angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products", "infinite-scroll"]).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 + $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*" diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 3fb5e69499..7dc9d1116f 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -22,6 +22,9 @@ //= require ./payment_methods/payment_methods //= require ./products/products //= require ./shipping_methods/shipping_methods +//= require ./utils/utils //= require ./users/users +//= require textAngular.min.js +//= require textAngular-sanitize.min.js //= require_tree . diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index c5b38191ba..cfcf6319af 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -1,8 +1,17 @@ angular.module("admin.enterprises") - .controller "enterpriseCtrl", ($scope, Enterprise, PaymentMethods, ShippingMethods) -> + .controller "enterpriseCtrl", ($scope, longDescription, NavigationCheck, Enterprise, PaymentMethods, ShippingMethods) -> $scope.Enterprise = Enterprise.enterprise $scope.PaymentMethods = PaymentMethods.paymentMethods $scope.ShippingMethods = ShippingMethods.shippingMethods + $scope.navClear = NavigationCheck.clear + # htmlVariable is used by textAngular wysiwyg for the long descrtiption. + $scope.htmlVariable = longDescription + + # Provide a callback for generating warning messages displayed before leaving the page. This is passed in + # from a directive "nav-check" in the page - if we pass it here it will be called in the test suite, + # and on all new uses of this contoller, and we might not want that . + $scope.enterpriseNavCallback = -> + "You are editing an enterprise!" for payment_method in $scope.PaymentMethods payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids @@ -32,4 +41,4 @@ angular.module("admin.enterprises") $scope.ShippingMethods.reduce (count, shipping_method) -> count++ if shipping_method.selected count - , 0 \ No newline at end of file + , 0 diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee index 6189661035..e1e43854d1 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.payment_methods", "admin.shipping_methods", "admin.users"]) \ No newline at end of file +angular.module("admin.enterprises", ["admin.payment_methods", "admin.utils", "admin.shipping_methods", "admin.users", "textAngular"]) \ No newline at end of file diff --git a/app/assets/javascripts/admin/enterprises/services/enterprise.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprise.js.coffee index b6e6a6147e..26061ef720 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprise.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprise.js.coffee @@ -1,4 +1,5 @@ angular.module("admin.enterprises") + # Populate Enterprise.enterprise with enterprise json array from the page. .factory 'Enterprise', (enterprise) -> new class Enterprise - enterprise: enterprise \ No newline at end of file + enterprise: enterprise diff --git a/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee b/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee new file mode 100644 index 0000000000..95f52505eb --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/navigation_check.js.coffee @@ -0,0 +1,9 @@ +angular.module("admin.utils").directive "navCheck", (NavigationCheck)-> + restrict: 'A' + scope: + navCallback: '&' + link: (scope,element,attributes) -> + # Define navigationCallback on a controller in scope, otherwise this default will be used: + scope.navCallback ||= -> + "You will lose any unsaved work!" + NavigationCheck.register(scope.navCallback) diff --git a/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee new file mode 100644 index 0000000000..ff1041474c --- /dev/null +++ b/app/assets/javascripts/admin/utils/services/navigation_check.js.coffee @@ -0,0 +1,46 @@ +angular.module("admin.utils") + .factory "NavigationCheck", ($window, $rootScope) -> + new class NavigationCheck + callbacks = [] + constructor: -> + if $window.addEventListener + $window.addEventListener "beforeunload", @onBeforeUnloadHandler + else + $window.onbeforeunload = @onBeforeUnloadHandler + + $rootScope.$on "$locationChangeStart", @locationChangeStartHandler + + + # Action for regular browser navigation. + onBeforeUnloadHandler: ($event) => + message = @getMessage() + if message + ($event or $window.event).preventDefault() + message + + # Action for angular navigation. + locationChangeStartHandler: ($event) => + message = @getMessage() + if message and not $window.confirm(message) + $event.stopPropagation() if $event.stopPropagation + $event.preventDefault() if $event.preventDefault + $event.cancelBubble = true + $event.returnValue = false + + # Runs callback functions to retreive most recently added non-empty message. + getMessage: -> + message = null + message = callback() ? message for callback in callbacks + message + + register: (callback) => + callbacks.push callback + + clear: => + if $window.addEventListener + $window.removeEventListener "beforeunload", @onBeforeUnloadHandler + else + $window.onbeforeunload = null + + $rootScope.$on "$locationChangeStart", null + diff --git a/app/assets/javascripts/admin/utils/utils.js.coffee b/app/assets/javascripts/admin/utils/utils.js.coffee new file mode 100644 index 0000000000..4d58ae930a --- /dev/null +++ b/app/assets/javascripts/admin/utils/utils.js.coffee @@ -0,0 +1 @@ +angular.module("admin.utils", []) \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index f529ac3255..45acfd6523 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -15,6 +15,8 @@ #= require ../shared/bindonce.min.js #= require ../shared/ng-infinite-scroll.min.js #= require ../shared/angular-local-storage.js +#= require angularjs-file-upload + #= require angular-rails-templates #= require_tree ../templates diff --git a/app/assets/javascripts/darkswarm/controllers/enterprise_image_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/enterprise_image_controller.js.coffee new file mode 100644 index 0000000000..e12d55b50c --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/enterprise_image_controller.js.coffee @@ -0,0 +1,13 @@ +angular.module('Darkswarm').controller "EnterpriseImageCtrl", ($scope, EnterpriseImageService) -> + $scope.imageStep = 'logo' + + $scope.imageSteps = ['logo', 'promo'] + + $scope.imageUploader = EnterpriseImageService.imageUploader + + $scope.imageSelect = (image_step) -> + EnterpriseImageService.imageSrc = null + $scope.imageStep = image_step + + $scope.imageSrc = -> + EnterpriseImageService.imageSrc diff --git a/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee similarity index 59% rename from app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee rename to app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee index 98053daebf..1e43c17465 100644 --- a/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee @@ -1,11 +1,14 @@ -Darkswarm.controller "HubsCtrl", ($scope, Hubs, Search, $document, $rootScope, HashNavigation, FilterSelectorsService) -> - $scope.Hubs = Hubs - $scope.hubs = Hubs.visible +Darkswarm.controller "EnterprisesCtrl", ($scope, Enterprises, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, EnterpriseModal) -> + $scope.Enterprises = Enterprises $scope.totalActive = FilterSelectorsService.totalActive $scope.clearAll = FilterSelectorsService.clearAll $scope.filterText = FilterSelectorsService.filterText $scope.FilterSelectorsService = FilterSelectorsService $scope.query = Search.search() + $scope.openModal = EnterpriseModal.open + $scope.activeTaxons = [] + $scope.show_profiles = false + $scope.filtersActive = false $scope.$watch "query", (query)-> Search.search query diff --git a/app/assets/javascripts/darkswarm/controllers/producers_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/producers_controller.js.coffee deleted file mode 100644 index d88af1e53d..0000000000 --- a/app/assets/javascripts/darkswarm/controllers/producers_controller.js.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Darkswarm.controller "ProducersCtrl", ($scope, Producers, $filter, FilterSelectorsService, Search) -> - $scope.Producers = Producers - $scope.totalActive = FilterSelectorsService.totalActive - $scope.clearAll = FilterSelectorsService.clearAll - $scope.filterText = FilterSelectorsService.filterText - $scope.FilterSelectorsService = FilterSelectorsService - $scope.filtersActive = false - $scope.activeTaxons = [] - $scope.query = Search.search() - - $scope.$watch "query", (query)-> - Search.search query diff --git a/app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee similarity index 84% rename from app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee rename to app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee index 2fcc0f32fd..3be0378619 100644 --- a/app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/registration/registration_controller.js.coffee @@ -3,7 +3,7 @@ Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, Enterpris $scope.enterprise = EnterpriseRegistrationService.enterprise $scope.select = RegistrationService.select - $scope.steps = ['details','address','contact','about','images','social'] + $scope.steps = ['details','contact','type','about','images','social'] $scope.countries = availableCountries diff --git a/app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/registration/registration_form_controller.js.coffee similarity index 88% rename from app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee rename to app/assets/javascripts/darkswarm/controllers/registration/registration_form_controller.js.coffee index 84f133da54..fabc2c382a 100644 --- a/app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/registration/registration_form_controller.js.coffee @@ -12,4 +12,4 @@ Darkswarm.controller "RegistrationFormCtrl", ($scope, RegistrationService, Enter EnterpriseRegistrationService.update(nextStep) if $scope.valid(form) $scope.selectIfValid = (nextStep, form) -> - RegistrationService.select(nextStep) if $scope.valid(form) \ No newline at end of file + RegistrationService.select(nextStep) if $scope.valid(form) diff --git a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee index 6db73f7af3..7a5ebb78f3 100644 --- a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee @@ -1,3 +1,4 @@ -Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises) -> - # Injecting Enterprises so CurrentHub.producers is dereferenced +Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises, EnterpriseModal) -> + # Injecting Enterprises so CurrentHub.producers is dereferenced. + # We should probably dereference here instead and separate out CurrentHub dereferencing from the Enterprise factory. $scope.CurrentHub = CurrentHub diff --git a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee index eb501bcb4a..0bbe0bbd23 100644 --- a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee @@ -1,18 +1,11 @@ Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location, OrderCycle) -> + # Return active if supplied path matches url hash path. $scope.active = (path)-> - if !OrderCycle.selected() and $location.hash() == "" and path == "about" - true - else - $location.hash() == path + $location.hash() == path - - $scope.tabs = ["contact", "about", "groups", "producers"] - for tab in $scope.tabs - $scope[tab] = - path: tab - - $scope.select = (tab)-> - if $scope.active(tab.path) + # Toggle tab selected status by setting the url hash path. + $scope.select = (path)-> + if $scope.active(path) $location.hash "" else - $location.hash tab.path + $location.hash path diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index 1e58fe7294..e8ea9dce3c 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,18 +1,19 @@ -window.Darkswarm = angular.module("Darkswarm", ["ngResource", - 'mm.foundation', - 'angularLocalStorage', - 'pasvaz.bindonce', - 'infinite-scroll', - 'angular-flash.service', +window.Darkswarm = angular.module("Darkswarm", ["ngResource", + 'mm.foundation', + 'angularLocalStorage', + 'pasvaz.bindonce', + 'infinite-scroll', + 'angular-flash.service', 'templates', 'ngSanitize', 'ngAnimate', 'google-maps', 'duScroll', + 'angularFileUpload', ]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) -> - $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') - $httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') - $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest' + $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') + $httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') + $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest' $httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*" # This allows us to trigger these two events on tooltips @@ -20,4 +21,3 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", # We manually handle our scrolling $anchorScrollProvider.disableAutoScrolling() - diff --git a/app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee similarity index 71% rename from app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee rename to app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee index af2b13f157..fa6378afe2 100644 --- a/app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/enterprise_modal.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.directive "producerModal", ($modal)-> +Darkswarm.directive "enterpriseModal", ($modal)-> restrict: 'E' replace: true template: "" @@ -6,5 +6,4 @@ Darkswarm.directive "producerModal", ($modal)-> link: (scope, elem, attrs, ctrl)-> elem.on "click", (ev)=> ev.stopPropagation() - scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'producer_modal.html', scope: scope) - + scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'enterprise_modal.html', scope: scope) diff --git a/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee deleted file mode 100644 index 6eb0299ab4..0000000000 --- a/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -Darkswarm.directive "hubModal", ($modal)-> - restrict: 'E' - replace: true - template: "{{enterprise.name}}" - link: (scope, elem, attrs, ctrl)-> - elem.on "click", (ev)=> - ev.stopPropagation() - scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'hub_modal.html', scope: scope) diff --git a/app/assets/javascripts/darkswarm/directives/inline_alert.js.coffee b/app/assets/javascripts/darkswarm/directives/inline_alert.js.coffee new file mode 100644 index 0000000000..9b37934375 --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/inline_alert.js.coffee @@ -0,0 +1,7 @@ +Darkswarm.directive "ofnInlineAlert", -> + restrict: 'A' + scope: true + link: (scope, elem, attrs) -> + scope.visible = true + scope.close = -> + scope.visible = false diff --git a/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee b/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee deleted file mode 100644 index 46550b854f..0000000000 --- a/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -Darkswarm.directive "ofnInlineFlash", -> - restrict: 'E' - controller: ($scope) -> - $scope.visible = true - $scope.closeFlash = -> - $scope.visible = false diff --git a/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee new file mode 100644 index 0000000000..2b38b3d31f --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/registration_limit_modal.js.coffee @@ -0,0 +1,13 @@ +Darkswarm.directive "ofnRegistrationLimitModal", (Navigation, $modal, Loading) -> + restrict: 'A' + link: (scope, elem, attr)-> + scope.modalInstance = $modal.open + templateUrl: 'registration/limit_reached.html' + windowClass: "login-modal register-modal xlarge" + backdrop: 'static' + + scope.modalInstance.result.then scope.close, scope.close + + scope.close = -> + Loading.message = "Taking you back to the home page" + Navigation.go "/" diff --git a/app/assets/javascripts/darkswarm/filters/capitalize.js.coffee b/app/assets/javascripts/darkswarm/filters/capitalize.js.coffee index acbd3fd637..5c2272135b 100644 --- a/app/assets/javascripts/darkswarm/filters/capitalize.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/capitalize.js.coffee @@ -1,4 +1,5 @@ Darkswarm.filter "capitalize", -> + # Convert to basic sentence case. (input, scope) -> - input = input.toLowerCase() if input? + input = input.toLowerCase() if input? input.substring(0, 1).toUpperCase() + input.substring(1) diff --git a/app/assets/javascripts/darkswarm/filters/filter_hubs.js.coffee b/app/assets/javascripts/darkswarm/filters/filter_hubs.js.coffee deleted file mode 100644 index b6adb84508..0000000000 --- a/app/assets/javascripts/darkswarm/filters/filter_hubs.js.coffee +++ /dev/null @@ -1,9 +0,0 @@ -Darkswarm.filter 'hubs', (Matcher)-> - (hubs, text) -> - hubs ||= [] - text ?= "" - - hubs.filter (hub)=> - Matcher.match [ - hub.name, hub.address.zipcode, hub.address.city, hub.address.state - ], text diff --git a/app/assets/javascripts/darkswarm/filters/filter_producers.js.coffee b/app/assets/javascripts/darkswarm/filters/filter_producers.js.coffee deleted file mode 100644 index ed5b8c1bea..0000000000 --- a/app/assets/javascripts/darkswarm/filters/filter_producers.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -Darkswarm.filter 'filterProducers', (hubsFilter)-> - (producers, text) -> - producers ||= [] - text ?= "" - hubsFilter(producers, text) - diff --git a/app/assets/javascripts/darkswarm/filters/localize_currency.js.coffee b/app/assets/javascripts/darkswarm/filters/localize_currency.js.coffee new file mode 100644 index 0000000000..7087e09fc3 --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/localize_currency.js.coffee @@ -0,0 +1,15 @@ +Darkswarm.filter "localizeCurrency", (currencyConfig)-> + # Convert number to string currency using injected currency configuration. + (amount) -> + # Set country code (eg. "US"). + currency_code = if currencyConfig.display_currency then " " + currencyConfig.currency else "" + # Set decimal points, 2 or 0 if hide_cents. + decimals = if currencyConfig.hide_cents == "true" then 0 else 2 + # We need to use parseFloat before toFixed as the amount should come in as a string. + amount_fixed = parseFloat(amount).toFixed(decimals) + + # Build the final price string. TODO use spree decimal point and spacer character settings. + if currencyConfig.symbol_position == 'before' + currencyConfig.symbol + amount_fixed + currency_code + else + amount_fixed + " " + currencyConfig.symbol + currency_code diff --git a/app/assets/javascripts/darkswarm/filters/search_enterprises.js.coffee b/app/assets/javascripts/darkswarm/filters/search_enterprises.js.coffee new file mode 100644 index 0000000000..2c42fe93d3 --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/search_enterprises.js.coffee @@ -0,0 +1,10 @@ +Darkswarm.filter 'searchEnterprises', (Matcher)-> + # Search multiple fields of enterprises for matching text fragment. + (enterprises, text) -> + enterprises ||= [] + text ?= "" + + enterprises.filter (enterprise)=> + Matcher.match [ + enterprise.name, enterprise.address.zipcode, enterprise.address.city, enterprise.address.state + ], text diff --git a/app/assets/javascripts/darkswarm/filters/show_hub_profiles.js.coffee b/app/assets/javascripts/darkswarm/filters/show_hub_profiles.js.coffee new file mode 100644 index 0000000000..b97a0c35e4 --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/show_hub_profiles.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.filter 'showHubProfiles', ()-> + # Filter hub_profile enterprises in or out. + (enterprises, show_profiles) -> + enterprises ||= [] + show_profiles ?= false + + enterprises.filter (enterprise)=> + show_profiles or enterprise.is_distributor diff --git a/app/assets/javascripts/darkswarm/filters/taxons.js.coffee b/app/assets/javascripts/darkswarm/filters/taxons.js.coffee index 3eb32ce1a4..a7ccc9b0cc 100644 --- a/app/assets/javascripts/darkswarm/filters/taxons.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/taxons.js.coffee @@ -1,13 +1,16 @@ -Darkswarm.filter 'taxons', (Matcher)-> - # Filter anything that responds to object.taxons, and/or object.primary_taxon +Darkswarm.filter 'taxons', ()-> + # Filter anything that responds to object.taxons, object.supplied_taxon or object.primary_taxon. (objects, ids) -> objects ||= [] ids ?= [] if ids.length == 0 + # No taxons selected, pass all objects through. objects else objects.filter (obj)-> taxons = obj.taxons - taxons.concat obj.supplied_taxons if obj.supplied_taxons + # Combine object taxons with supplied taxons, if they exist. + taxons = taxons.concat obj.supplied_taxons if obj.supplied_taxons + # Match primary taxon if it exists, then taxon array. obj.primary_taxon?.id in ids || taxons.some (taxon)-> taxon.id in ids diff --git a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee index 5f5ae520e3..f2d7049e67 100644 --- a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections)-> +Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections, Loading)-> new class AuthenticationService selectedPath: "/login" @@ -25,4 +25,9 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir active: Navigation.active close: -> - Navigation.navigate "/" + if location.pathname == "/" + Navigation.navigate "/" + else + Loading.message = "Taking you back to the home page" + location.hash = "" + location.pathname = "/" diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index def1c9b6d0..2b58bb70ae 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -3,7 +3,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)-> new class Cart dirty: false order: CurrentOrder.order - line_items: CurrentOrder.order?.line_items || [] + line_items: CurrentOrder.order?.line_items || [] constructor: -> for line_item in @line_items line_item.variant.line_item = line_item @@ -22,13 +22,13 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)-> # TODO what shall we do here? data: => - variants = {} + variants = {} for li in @line_items_present() - variants[li.variant.id] = + variants[li.variant.id] = quantity: li.quantity max_quantity: li.max_quantity {variants: variants} - + saved: => @dirty = false @@ -48,15 +48,15 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)-> total: => @line_items_present().map (li)-> - li.variant.getPrice() + li.variant.totalPrice() .reduce (t, price)-> t + price , 0 register_variant: (variant)=> exists = @line_items.some (li)-> li.variant == variant - @create_line_item(variant) unless exists - + @create_line_item(variant) unless exists + create_line_item: (variant)-> variant.line_item = variant: variant diff --git a/app/assets/javascripts/darkswarm/services/current_hub.js.coffee b/app/assets/javascripts/darkswarm/services/current_hub.js.coffee index 5e6a385b8a..ade8d75d18 100644 --- a/app/assets/javascripts/darkswarm/services/current_hub.js.coffee +++ b/app/assets/javascripts/darkswarm/services/current_hub.js.coffee @@ -1,3 +1,4 @@ -Darkswarm.factory 'CurrentHub', ($location, $filter, currentHub) -> +Darkswarm.factory 'CurrentHub', (currentHub) -> + # Populate CurrentHub.hub from json in page. This is probably redundant now. new class CurrentHub hub: currentHub diff --git a/app/assets/javascripts/darkswarm/services/current_order.js.coffee b/app/assets/javascripts/darkswarm/services/current_order.js.coffee index cd2402f0c0..6570ddcd06 100644 --- a/app/assets/javascripts/darkswarm/services/current_order.js.coffee +++ b/app/assets/javascripts/darkswarm/services/current_order.js.coffee @@ -1,3 +1,4 @@ Darkswarm.factory 'CurrentOrder', (currentOrder) -> + # Populate Currentorder.order from json in page. This is probably redundant now. new class CurrentOrder order: currentOrder diff --git a/app/assets/javascripts/darkswarm/services/enterprise_image_service.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_image_service.js.coffee new file mode 100644 index 0000000000..c8cd64e93a --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/enterprise_image_service.js.coffee @@ -0,0 +1,12 @@ +Darkswarm.factory "EnterpriseImageService", (FileUploader, spreeApiKey) -> + new class EnterpriseImageService + imageSrc: null + + imageUploader: new FileUploader + headers: + 'X-Spree-Token': spreeApiKey + autoUpload: true + + configure: (enterprise) => + @imageUploader.url = "/api/enterprises/#{enterprise.id}/update_image" + @imageUploader.onSuccessItem = (image, response) => @imageSrc = response diff --git a/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee new file mode 100644 index 0000000000..a0fb3ce5c5 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.factory "EnterpriseModal", ($modal, $rootScope)-> + # Build a modal popup for an enterprise. + new class EnterpriseModal + open: (enterprise)-> + scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise + + scope.enterprise = enterprise + $modal.open(templateUrl: "enterprise_modal.html", scope: scope) 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 68915193ee..77b2204316 100644 --- a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, CurrentUser, spreeApiKey, Loading, availableCountries, enterpriseAttributes) -> +Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, EnterpriseImageService, CurrentUser, spreeApiKey, Loading, availableCountries, enterpriseAttributes) -> new class EnterpriseRegistrationService enterprise: user_ids: [CurrentUser.id] @@ -22,6 +22,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, ).success((data) => Loading.clear() @enterprise.id = data + EnterpriseImageService.configure(@enterprise) RegistrationService.select('about') ).error((data) => Loading.clear() @@ -54,4 +55,4 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, enterprise[key] = value enterprise.address_attributes = @enterprise.address if @enterprise.address? enterprise.address_attributes.country_id = @enterprise.country.id if @enterprise.country? - enterprise \ No newline at end of file + enterprise diff --git a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee index 50143e006b..6040f1d150 100644 --- a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee @@ -1,13 +1,21 @@ -Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer)-> +Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter)-> new class Enterprises - enterprises_by_id: {} # id/object pairs for lookup + enterprises_by_id: {} constructor: -> + # Populate Enterprises.enterprises from json in page. @enterprises = enterprises + # Map enterprises to id/object pairs for lookup. for enterprise in enterprises @enterprises_by_id[enterprise.id] = enterprise + # Replace enterprise and taxons ids with actual objects. @dereferenceEnterprises() @dereferenceTaxons() - + @visible_enterprises = visibleFilter @enterprises + @producers = @visible_enterprises.filter (enterprise)-> + enterprise.category in ["producer_hub", "producer_shop", "producer"] + @hubs = @visible_enterprises.filter (enterprise)-> + enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"] + dereferenceEnterprises: -> if CurrentHub.hub?.id CurrentHub.hub = @enterprises_by_id[CurrentHub.hub.id] @@ -16,6 +24,7 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer) Dereferencer.dereference enterprise.producers, @enterprises_by_id dereferenceTaxons: -> - for enterprise in @enterprises + for enterprise in @enterprises Dereferencer.dereference enterprise.taxons, Taxons.taxons_by_id Dereferencer.dereference enterprise.supplied_taxons, Taxons.taxons_by_id + diff --git a/app/assets/javascripts/darkswarm/services/hubs.js.coffee b/app/assets/javascripts/darkswarm/services/hubs.js.coffee deleted file mode 100644 index de9900866f..0000000000 --- a/app/assets/javascripts/darkswarm/services/hubs.js.coffee +++ /dev/null @@ -1,9 +0,0 @@ -Darkswarm.factory 'Hubs', ($filter, Enterprises, visibleFilter) -> - new class Hubs - constructor: -> - @hubs = @order Enterprises.enterprises.filter (hub)-> - hub.is_distributor && hub.has_shopfront - @visible = visibleFilter @hubs - - order: (hubs)-> - $filter('orderBy')(hubs, ['-active', '+orders_close_at']) diff --git a/app/assets/javascripts/darkswarm/services/map.js.coffee b/app/assets/javascripts/darkswarm/services/map.js.coffee index 43750acdb2..703c3c54bf 100644 --- a/app/assets/javascripts/darkswarm/services/map.js.coffee +++ b/app/assets/javascripts/darkswarm/services/map.js.coffee @@ -1,7 +1,7 @@ -Darkswarm.factory "OfnMap", (Enterprises, MapModal, visibleFilter)-> +Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter)-> new class OfnMap constructor: -> - @enterprises = (@extend(enterprise) for enterprise in visibleFilter(Enterprises.enterprises)) + @enterprises = (@extend(enterprise) for enterprise in visibleFilter(Enterprises.enterprises)) # Adding methods to each enterprise @@ -14,4 +14,4 @@ Darkswarm.factory "OfnMap", (Enterprises, MapModal, visibleFilter)-> icon: enterprise.icon id: enterprise.id reveal: => - MapModal.open enterprise + EnterpriseModal.open enterprise diff --git a/app/assets/javascripts/darkswarm/services/map_modal.js.coffee b/app/assets/javascripts/darkswarm/services/map_modal.js.coffee deleted file mode 100644 index c9ed30f558..0000000000 --- a/app/assets/javascripts/darkswarm/services/map_modal.js.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Darkswarm.factory "MapModal", ($modal, $rootScope)-> - new class MapModal - open: (enterprise)-> - scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise - - scope.enterprise = enterprise - if enterprise.is_distributor - scope.hub = enterprise - $modal.open(templateUrl: "hub_modal.html", scope: scope) - else - scope.producer = enterprise - $modal.open(templateUrl: "map_modal_producer.html", scope: scope) diff --git a/app/assets/javascripts/darkswarm/services/matcher.js.coffee b/app/assets/javascripts/darkswarm/services/matcher.js.coffee index aadd6a4128..9360afdd1f 100644 --- a/app/assets/javascripts/darkswarm/services/matcher.js.coffee +++ b/app/assets/javascripts/darkswarm/services/matcher.js.coffee @@ -1,6 +1,7 @@ Darkswarm.factory "Matcher", -> - new class Matcher - match: (properties, text)-> - properties.some (prop)-> - prop ||= "" - prop.toLowerCase().indexOf(text.toLowerCase()) != -1 + # Match text fragment in an array of strings. + new class Matcher + match: (properties, text)-> + properties.some (prop)-> + prop ||= "" + prop.toLowerCase().indexOf(text.toLowerCase()) != -1 diff --git a/app/assets/javascripts/darkswarm/services/producers.js.coffee b/app/assets/javascripts/darkswarm/services/producers.js.coffee deleted file mode 100644 index 65d8e42c5d..0000000000 --- a/app/assets/javascripts/darkswarm/services/producers.js.coffee +++ /dev/null @@ -1,7 +0,0 @@ -Darkswarm.factory 'Producers', (Enterprises, visibleFilter) -> - new class Producers - constructor: -> - @producers = Enterprises.enterprises.filter (enterprise)-> - enterprise.is_primary_producer - @visible = visibleFilter @producers - diff --git a/app/assets/javascripts/darkswarm/services/registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/registration_service.js.coffee index a2a1fe2dc4..530d118025 100644 --- a/app/assets/javascripts/darkswarm/services/registration_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/registration_service.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory "RegistrationService", (Navigation, $modal, Loading)-> +angular.module('Darkswarm').factory "RegistrationService", (Navigation, $modal, Loading)-> new class RegistrationService constructor: -> @@ -20,4 +20,4 @@ Darkswarm.factory "RegistrationService", (Navigation, $modal, Loading)-> close: -> Loading.message = "Taking you back to the home page" - Navigation.go "/" \ No newline at end of file + Navigation.go "/" diff --git a/app/assets/javascripts/darkswarm/services/taxons.js.coffee b/app/assets/javascripts/darkswarm/services/taxons.js.coffee index cbe6c118e0..984eb0df22 100644 --- a/app/assets/javascripts/darkswarm/services/taxons.js.coffee +++ b/app/assets/javascripts/darkswarm/services/taxons.js.coffee @@ -1,8 +1,10 @@ Darkswarm.factory "Taxons", (taxons)-> new class Taxons - taxons: taxons + # Populate Taxons.taxons from json in page. + taxons: taxons taxons_by_id: {} constructor: -> + # Map taxons to id/object pairs for lookup. for taxon in @taxons @taxons_by_id[taxon.id] = taxon diff --git a/app/assets/javascripts/darkswarm/services/variants.js.coffee b/app/assets/javascripts/darkswarm/services/variants.js.coffee index 0f231ac030..6562bd9e0b 100644 --- a/app/assets/javascripts/darkswarm/services/variants.js.coffee +++ b/app/assets/javascripts/darkswarm/services/variants.js.coffee @@ -5,7 +5,8 @@ Darkswarm.factory 'Variants', -> @variants[variant.id] ||= @extend variant extend: (variant)-> - variant.getPrice = -> - variant.price * variant.line_item.quantity - variant.basePricePercentage = Math.round(variant.base_price / variant.price * 100) + # Add totalPrice method to calculate line item total. This should be on a line item! + variant.totalPrice = -> + variant.price_with_fees * variant.line_item.quantity + variant.basePricePercentage = Math.round(variant.price / variant.price_with_fees * 100) variant diff --git a/app/assets/javascripts/search/all.js b/app/assets/javascripts/search/all.js deleted file mode 100644 index c684eed4b1..0000000000 --- a/app/assets/javascripts/search/all.js +++ /dev/null @@ -1,18 +0,0 @@ -// This is a manifest file that'll be compiled into including all the files listed below. -// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically -// be included in the compiled file accessible from http://example.com/assets/application.js -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. -// - -//= require jquery -//= require jquery_ujs -//= require jquery-ui -//= require spin -//= require foundation -//= require_tree . -// - -// Hacky fix for issue - http://foundation.zurb.com/forum/posts/2112-foundation-5100-syntax-error-in-js -Foundation.set_namespace = function() {}; -$(function(){ $(document).foundation(); }); diff --git a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.base.js.coffee b/app/assets/javascripts/search/gmaps4rails/gmaps4rails.base.js.coffee deleted file mode 100644 index 684215a438..0000000000 --- a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.base.js.coffee +++ /dev/null @@ -1,444 +0,0 @@ -Gmaps = {} - -Gmaps.triggerOldOnload = -> - Gmaps.oldOnload() if typeof(Gmaps.oldOnload) == 'function' - -Gmaps.loadMaps = -> - #loop through all variable names. - #there should only be maps inside so it trigger their load function - for key, value of Gmaps - searchLoadIncluded = key.search(/load/) - if searchLoadIncluded == -1 - load_function_name = "load_" + key - Gmaps[load_function_name]() - -window.Gmaps = Gmaps - -class @Gmaps4Rails - - constructor: -> - #map config - @map = null #DEPRECATED: will still contain a copy of serviceObject below as transition - @serviceObject = null #contains the map we're working on - @visibleInfoWindow = null #contains the current opened infowindow - @userLocation = null #contains user's location if geolocalization was performed and successful - - #empty slots - @geolocationSuccess = -> false #triggered when geolocation succeeds. Can be customized. - @geolocationFailure = -> false #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean - @callback = -> false #to let user set a custom callback function - @customClusterer = -> false #to let user set custom clusterer pictures - @infobox = -> false #to let user use custom infoboxes - @jsTemplate = false #to let user create infowindows client side - - @default_map_options = - id: 'map' - draggable: true - detect_location: false # should the browser attempt to use geolocation detection features of HTML5? - center_on_user: false # centers map on the location detected through the browser - center_latitude: 0 - center_longitude: 0 - zoom: 7 - maxZoom: null - minZoom: null - auto_adjust : true # adjust the map to the markers if set to true - auto_zoom: true # zoom given by auto-adjust - bounds: [] # adjust map to these limits. Should be [{"lat": , "lng": }] - raw: {} # raw json to pass additional options - - @default_markers_conf = - #Marker config - title: "" - #MarkerImage config - picture : "" - width: 22 - length: 32 - draggable: false # how to modify: <%= gmaps( "markers" => { "data" => @object.to_gmaps4rails, "options" => { "draggable" => true }}) %> - #clustering config - do_clustering: false # do clustering if set to true - randomize: false # Google maps can't display two markers which have the same coordinates. This randomizer enables to prevent this situation from happening. - max_random_distance: 100 # in meters. Each marker coordinate could be altered by this distance in a random direction - list_container: null # id of the ul that will host links to all markers - offset: 0 # used when adding_markers to an existing map. Because new markers are concated with previous one, offset is here to prevent the existing from being re-created. - raw: {} # raw json to pass additional options - - #Stored variables - @markers = [] # contains all markers. A marker contains the following: {"description": , "longitude": , "title":, "latitude":, "picture": "", "width": "", "length": "", "sidebar": "", "serviceObject": google_marker} - @boundsObject = null # contains current bounds from markers, polylines etc... - @polygons = [] # contains raw data, array of arrays (first element could be a hash containing options) - @polylines = [] # contains raw data, array of arrays (first element could be a hash containing options) - @circles = [] # contains raw data, array of hash - @markerClusterer = null # contains all marker clusterers - @markerImages = [] - - #Polyline Styling - @polylines_conf = #default style for polylines - strokeColor: "#FF0000" - strokeOpacity: 1 - strokeWeight: 2 - clickable: false - zIndex: null - - #tnitializes the map - initialize : -> - @serviceObject = @createMap() - @map = @serviceObject #beware, soon deprecated - if (@map_options.detect_location == true or @map_options.center_on_user == true) - @findUserLocation(this) - #resets sidebar if needed - @resetSidebarContent() - - findUserLocation : (map_object) -> - if (navigator.geolocation) - #try to retrieve user's position - positionSuccessful = (position) -> - map_object.userLocation = map_object.createLatLng(position.coords.latitude, position.coords.longitude) - #change map's center to focus on user's geoloc if asked - if(map_object.map_options.center_on_user == true) - map_object.centerMapOnUser() - map_object.geolocationSuccess() - positionFailure = -> - map_object.geolocationFailure(true) - - navigator.geolocation.getCurrentPosition( positionSuccessful, positionFailure) - else - #failure but the navigator doesn't handle geolocation - map_object.geolocationFailure(false) - - - #//////////////////////////////////////////////////// - #//////////////////// DIRECTIONS //////////////////// - #//////////////////////////////////////////////////// - - create_direction : -> - directionsDisplay = new google.maps.DirectionsRenderer() - directionsService = new google.maps.DirectionsService() - - directionsDisplay.setMap(@serviceObject) - #display panel only if required - if @direction_conf.display_panel - directionsDisplay.setPanel(document.getElementById(@direction_conf.panel_id)) - - directionsDisplay.setOptions - suppressMarkers: false - suppressInfoWindows: false - suppressPolylines: false - - request = - origin: @direction_conf.origin - destination: @direction_conf.destination - waypoints: @direction_conf.waypoints - optimizeWaypoints: @direction_conf.optimizeWaypoints - unitSystem: google.maps.DirectionsUnitSystem[@direction_conf.unitSystem] - avoidHighways: @direction_conf.avoidHighways - avoidTolls: @direction_conf.avoidTolls - region: @direction_conf.region - travelMode: google.maps.DirectionsTravelMode[@direction_conf.travelMode] - language: "en" - - directionsService.route request, (response, status) -> - if (status == google.maps.DirectionsStatus.OK) - directionsDisplay.setDirections(response) - - #//////////////////////////////////////////////////// - #///////////////////// CIRCLES ////////////////////// - #//////////////////////////////////////////////////// - - #Loops through all circles - #Loops through all circles and draws them - create_circles : -> - for circle in @circles - @create_circle circle - - create_circle : (circle) -> - #by convention, default style configuration could be integrated in the first element - if circle == @circles[0] - @circles_conf.strokeColor = circle.strokeColor if circle.strokeColor? - @circles_conf.strokeOpacity = circle.strokeOpacity if circle.strokeOpacity? - @circles_conf.strokeWeight = circle.strokeWeight if circle.strokeWeight? - @circles_conf.fillColor = circle.fillColor if circle.fillColor? - @circles_conf.fillOpacity = circle.fillOpacity if circle.fillOpacity? - - if circle.lat? and circle.lng? - # always check if a config is given, if not, use defaults - # NOTE: is there a cleaner way to do this? Maybe a hash merge of some sort? - newCircle = new google.maps.Circle - center: @createLatLng(circle.lat, circle.lng) - strokeColor: circle.strokeColor || @circles_conf.strokeColor - strokeOpacity: circle.strokeOpacity || @circles_conf.strokeOpacity - strokeWeight: circle.strokeWeight || @circles_conf.strokeWeight - fillOpacity: circle.fillOpacity || @circles_conf.fillOpacity - fillColor: circle.fillColor || @circles_conf.fillColor - clickable: circle.clickable || @circles_conf.clickable - zIndex: circle.zIndex || @circles_conf.zIndex - radius: circle.radius - - circle.serviceObject = newCircle - newCircle.setMap(@serviceObject) - - # clear circles - clear_circles : -> - for circle in @circles - @clear_circle circle - - clear_circle : (circle) -> - circle.serviceObject.setMap(null) - - hide_circles : -> - for circle in @circles - @hide_circle circle - - hide_circle : (circle) -> - circle.serviceObject.setMap(null) - - show_circles : -> - for circle in @circles - @show_circle @circle - - show_circle : (circle) -> - circle.serviceObject.setMap(@serviceObject) - - #//////////////////////////////////////////////////// - #///////////////////// POLYGONS ///////////////////// - #//////////////////////////////////////////////////// - - #polygons is an array of arrays. It loops. - create_polygons : -> - for polygon in @polygons - @create_polygon(polygon) - - #creates a single polygon, triggered by create_polygons - create_polygon : (polygon) -> - polygon_coordinates = [] - - #Polygon points are in an Array, that's why looping is necessary - for point in polygon - latlng = @createLatLng(point.lat, point.lng) - polygon_coordinates.push(latlng) - #first element of an Array could contain specific configuration for this particular polygon. If no config given, use default - if point == polygon[0] - strokeColor = point.strokeColor || @polygons_conf.strokeColor - strokeOpacity = point.strokeOpacity || @polygons_conf.strokeOpacity - strokeWeight = point.strokeWeight || @polygons_conf.strokeWeight - fillColor = point.fillColor || @polygons_conf.fillColor - fillOpacity = point.fillOpacity || @polygons_conf.fillOpacity - clickable = point.clickable || @polygons_conf.clickable - - #Construct the polygon - new_poly = new google.maps.Polygon - paths: polygon_coordinates - strokeColor: strokeColor - strokeOpacity: strokeOpacity - strokeWeight: strokeWeight - fillColor: fillColor - fillOpacity: fillOpacity - clickable: clickable - map: @serviceObject - - #save polygon in list - polygon.serviceObject = new_poly - - - - #//////////////////////////////////////////////////// - #///////////////////// MARKERS ////////////////////// - #//////////////////////////////////////////////////// - - #creates, clusterizes and adjusts map - create_markers : -> - @createServiceMarkersFromMarkers() - @clusterize() - - #create google.maps Markers from data provided by user - createServiceMarkersFromMarkers : -> - for marker, index in @markers - if not @markers[index].serviceObject? - #extract options, test if value passed or use default - Lat = @markers[index].lat - Lng = @markers[index].lng - - #alter coordinates if randomize is true - if @markers_conf.randomize - LatLng = @randomize(Lat, Lng) - #retrieve coordinates from the array - Lat = LatLng[0] - Lng = LatLng[1] - - #save object - @markers[index].serviceObject = @createMarker - "marker_picture": if @markers[index].picture then @markers[index].picture else @markers_conf.picture - "marker_width": if @markers[index].width then @markers[index].width else @markers_conf.width - "marker_height": if @markers[index].height then @markers[index].height else @markers_conf.length - "marker_title": if @markers[index].title then @markers[index].title else null - "marker_anchor": if @markers[index].marker_anchor then @markers[index].marker_anchor else null - "shadow_anchor": if @markers[index].shadow_anchor then @markers[index].shadow_anchor else null - "shadow_picture": if @markers[index].shadow_picture then @markers[index].shadow_picture else null - "shadow_width": if @markers[index].shadow_width then @markers[index].shadow_width else null - "shadow_height": if @markers[index].shadow_height then @markers[index].shadow_height else null - "marker_draggable": if @markers[index].draggable then @markers[index].draggable else @markers_conf.draggable - "rich_marker": if @markers[index].rich_marker then @markers[index].rich_marker else null - "zindex": if @markers[index].zindex then @markers[index].zindex else null - "Lat": Lat - "Lng": Lng - "index": index - - #add infowindowstuff if enabled - @createInfoWindow(@markers[index]) - #create sidebar if enabled - @createSidebar(@markers[index]) - - @markers_conf.offset = @markers.length - - #creates Image Anchor Position or return null if nothing passed - createImageAnchorPosition : (anchorLocation) -> - if (anchorLocation == null) - return null - else - return @createPoint(anchorLocation[0], anchorLocation[1]) - - - #replace old markers with new markers on an existing map - replaceMarkers : (new_markers, adjustBounds = true) -> - @clearMarkers() - #reset previous markers - @markers = new Array - #reset current bounds - @boundsObject = @createLatLngBounds() if adjustBounds - #reset sidebar content if exists - @resetSidebarContent() - #add new markers - @markers_conf.offset = 0 - @addMarkers(new_markers, adjustBounds) - - #add new markers to on an existing map - addMarkers : (new_markers, adjustBounds = true) -> - #update the list of markers to take into account - @markers = @markers.concat(new_markers) - #put markers on the map - @create_markers() - @adjustMapToBounds() if adjustBounds - - #//////////////////////////////////////////////////// - #///////////////////// SIDEBAR ////////////////////// - #//////////////////////////////////////////////////// - - #//creates sidebar - createSidebar : (marker_container) -> - if (@markers_conf.list_container) - ul = document.getElementById(@markers_conf.list_container) - li = document.createElement('li') - aSel = document.createElement('a') - aSel.href = 'javascript:void(0);' - html = if marker_container.sidebar? then marker_container.sidebar else "Marker" - aSel.innerHTML = html - currentMap = this - aSel.onclick = @sidebar_element_handler(currentMap, marker_container.serviceObject, 'click') - li.appendChild(aSel) - ul.appendChild(li) - - #moves map to marker clicked + open infowindow - sidebar_element_handler : (currentMap, marker, eventType) -> - return () -> - currentMap.map.panTo(marker.position) - google.maps.event.trigger(marker, eventType) - - - resetSidebarContent : -> - if @markers_conf.list_container isnt null - ul = document.getElementById(@markers_conf.list_container) - ul.innerHTML = "" - - #//////////////////////////////////////////////////// - #////////////////// MISCELLANEOUS /////////////////// - #//////////////////////////////////////////////////// - - #to make the map fit the different LatLng points - adjustMapToBounds : -> - #FIRST_STEP: retrieve all bounds - #create the bounds object only if necessary - if @map_options.auto_adjust or @map_options.bounds isnt null - @boundsObject = @createLatLngBounds() - - #if autodjust is true, must get bounds from markers polylines etc... - if @map_options.auto_adjust - #from markers - @extendBoundsWithMarkers() - - #from polylines: - @updateBoundsWithPolylines() - - #from polygons: - @updateBoundsWithPolygons() - - #from circles - @updateBoundsWithCircles() - - #in every case, I've to take into account the bounds set up by the user - @extendMapBounds() - - #SECOND_STEP: ajust the map to the bounds - @adaptMapToBounds() - - #//////////////////////////////////////////////////// - #/////////////////// POLYLINES ////////////////////// - #//////////////////////////////////////////////////// - - #replace old markers with new markers on an existing map - replacePolylines : (new_polylines) -> - #reset previous polylines and kill them from map - @destroy_polylines() - #set new polylines - @polylines = new_polylines - #create - @create_polylines() - #.... and adjust map boundaries - @adjustMapToBounds() - - destroy_polylines : -> - for polyline in @polylines - #delete polylines from map - polyline.serviceObject.setMap(null) - #empty array - @polylines = [] - - #polylines is an array of arrays. It loops. - create_polylines : -> - for polyline in @polylines - @create_polyline polyline - - #//////////////////////////////////////////////////// - #///////////////// Basic functions ////////////////// - #///////////////////tests coded////////////////////// - - #//basic function to check existence of a variable - exists : (var_name) -> - return (var_name != "" and typeof var_name != "undefined") - - - #randomize - randomize : (Lat0, Lng0) -> - #distance in meters between 0 and max_random_distance (positive or negative) - dx = @markers_conf.max_random_distance * @random() - dy = @markers_conf.max_random_distance * @random() - Lat = parseFloat(Lat0) + (180/Math.PI)*(dy/6378137) - Lng = parseFloat(Lng0) + ( 90/Math.PI)*(dx/6378137)/Math.cos(Lat0) - return [Lat, Lng] - - mergeObjectWithDefault : (object1, object2) -> - copy_object1 = {} - for key, value of object1 - copy_object1[key] = value - - for key, value of object2 - unless copy_object1[key]? - copy_object1[key] = value - return copy_object1 - - mergeWithDefault : (objectName) -> - default_object = @["default_" + objectName] - object = @[objectName] - @[objectName] = @mergeObjectWithDefault(object, default_object) - return true - - #gives a value between -1 and 1 - random : -> return(Math.random() * 2 -1) diff --git a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.bing.js.coffee b/app/assets/javascripts/search/gmaps4rails/gmaps4rails.bing.js.coffee deleted file mode 100644 index 9eb53a6b76..0000000000 --- a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.bing.js.coffee +++ /dev/null @@ -1,174 +0,0 @@ -###################################################################################################### -############################################## Bing Maps ########################################## -###################################################################################################### - -#// http://wiki.openstreetmap.org/wiki/OpenLayers -#// http://openlayers.org/dev/examples -#//http://docs.openlayers.org/contents.html - -class @Gmaps4RailsBing extends Gmaps4Rails - - constructor: -> - super - @map_options = - type: "road" # aerial, auto, birdseye, collinsBart, mercator, ordnanceSurvey, road - @markers_conf = - infobox: "description" #description or htmlContent - - @mergeWithDefault("map_options") - @mergeWithDefault("markers_conf") - - #//////////////////////////////////////////////////// - #/////////////// Basic Objects ////////////// - #//////////////////////////////////////////////////// - - getMapType: -> - switch @map_options.type - when "road" then return Microsoft.Maps.MapTypeId.road - when "aerial" then return Microsoft.Maps.MapTypeId.aerial - when "auto" then return Microsoft.Maps.MapTypeId.auto - when "birdseye" then return Microsoft.Maps.MapTypeId.birdseye - when "collinsBart" then return Microsoft.Maps.MapTypeId.collinsBart - when "mercator" then return Microsoft.Maps.MapTypeId.mercator - when "ordnanceSurvey" then return Microsoft.Maps.MapTypeId.ordnanceSurvey - else return Microsoft.Maps.MapTypeId.auto - - createPoint: (lat, lng) -> - return new Microsoft.Maps.Point(lat, lng) - - createLatLng:(lat, lng) -> - return new Microsoft.Maps.Location(lat, lng) - - createLatLngBounds: -> - - createMap: -> - return new Microsoft.Maps.Map(document.getElementById(@map_options.id), { - credentials: @map_options.provider_key, - mapTypeId: @getMapType(), - center: @createLatLng(@map_options.center_latitude, @map_options.center_longitude), - zoom: @map_options.zoom - }) - - createSize: (width, height) -> - return new google.maps.Size(width, height) - - #//////////////////////////////////////////////////// - #////////////////////// Markers ///////////////////// - #//////////////////////////////////////////////////// - - createMarker: (args) -> - markerLatLng = @createLatLng(args.Lat, args.Lng) - marker - #// Marker sizes are expressed as a Size of X,Y - if args.marker_picture == "" - marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), { - draggable: args.marker_draggable, - anchor: @createImageAnchorPosition(args.Lat, args.Lng), - text: args.marker_title - } - ); - else - marker = new Microsoft.Maps.Pushpin(@createLatLng(args.Lat, args.Lng), { - draggable: args.marker_draggable, - anchor: @createImageAnchorPosition(args.Lat, args.Lng), - icon: args.marker_picture, - height: args.marker_height, - text: args.marker_title, - width: args.marker_width - } - ); - @addToMap(marker) - return marker - - #// clear markers - clearMarkers: -> - for marker in @markers - @clearMarker marker - - clearMarker: (marker) -> - @removeFromMap(marker.serviceObject) - - #//show and hide markers - showMarkers: -> - for marker in @markers - @showMarker marker - - showMarker: (marker) -> - marker.serviceObject.setOptions({ visible: true }) - - hideMarkers: -> - for marker in @markers - @hideMarker marker - - hideMarker: (marker) -> - marker.serviceObject.setOptions({ visible: false }) - - extendBoundsWithMarkers: -> - locationsArray = [] - for marker in @markers - locationsArray.push(marker.serviceObject.getLocation()) - @boundsObject = Microsoft.Maps.LocationRect.fromLocations(locationsArray) - - #//////////////////////////////////////////////////// - #/////////////////// Clusterer ////////////////////// - #//////////////////////////////////////////////////// - - createClusterer: (markers_array) -> - - clearClusterer: -> - - #//creates clusters - clusterize: -> - - #//////////////////////////////////////////////////// - #/////////////////// INFO WINDOW //////////////////// - #//////////////////////////////////////////////////// - - #// creates infowindows - createInfoWindow: (marker_container) -> - if marker_container.description? - #//create the infowindow - if @markers_conf.infobox == "description" - marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { description: marker_container.description, visible: false, showCloseButton: true}) - else - marker_container.info_window = new Microsoft.Maps.Infobox(marker_container.serviceObject.getLocation(), { htmlContent: marker_container.description, visible: false}) - - #//add the listener associated - currentMap = this - Microsoft.Maps.Events.addHandler(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.info_window)) - @addToMap(marker_container.info_window) - - openInfoWindow: (currentMap, infoWindow) -> - return -> - # Close the latest selected marker before opening the current one. - if currentMap.visibleInfoWindow - currentMap.visibleInfoWindow.setOptions({ visible: false }) - infoWindow.setOptions({ visible:true }) - currentMap.visibleInfoWindow = infoWindow - - #//////////////////////////////////////////////////// - #/////////////////// Other methods ////////////////// - #//////////////////////////////////////////////////// - - fitBounds: -> - @serviceObject.setView({bounds: @boundsObject}) - - addToMap: (object)-> - @serviceObject.entities.push(object) - - removeFromMap: (object)-> - @serviceObject.entities.remove(object) - - centerMapOnUser: -> - @serviceObject.setView({ center: @userLocation}) - - updateBoundsWithPolylines: ()-> - - updateBoundsWithPolygons: ()-> - - updateBoundsWithCircles: ()-> - - extendMapBounds :-> - - adaptMapToBounds: -> - @fitBounds() \ No newline at end of file diff --git a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.googlemaps.js.coffee b/app/assets/javascripts/search/gmaps4rails/gmaps4rails.googlemaps.js.coffee deleted file mode 100644 index ed52ddc15a..0000000000 --- a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.googlemaps.js.coffee +++ /dev/null @@ -1,339 +0,0 @@ -####################################################################################################### -############################################## Google maps ########################################## -####################################################################################################### - -class @Gmaps4RailsGoogle extends Gmaps4Rails - - constructor: -> - super - #Map settings - @map_options = - disableDefaultUI: false - disableDoubleClickZoom: false - type: "ROADMAP" # HYBRID, ROADMAP, SATELLITE, TERRAIN - - #markers + info styling - @markers_conf = - clusterer_gridSize: 50 - clusterer_maxZoom: 5 - custom_cluster_pictures: null - custom_infowindow_class: null - - @mergeWithDefault("map_options") - @mergeWithDefault("markers_conf") - - @kml_options = - clickable: true - preserveViewport: false - suppressInfoWindows: false - - #Polygon Styling - @polygons_conf = # default style for polygons - strokeColor: "#FFAA00" - strokeOpacity: 0.8 - strokeWeight: 2 - fillColor: "#000000" - fillOpacity: 0.35 - clickable: false - - #Circle Styling - @circles_conf = #default style for circles - fillColor: "#00AAFF" - fillOpacity: 0.35 - strokeColor: "#FFAA00" - strokeOpacity: 0.8 - strokeWeight: 2 - clickable: false - zIndex: null - - #Direction Settings - @direction_conf = - panel_id: null - display_panel: false - origin: null - destination: null - waypoints: [] #[{location: "toulouse,fr", stopover: true}, {location: "Clermont-Ferrand, fr", stopover: true}] - optimizeWaypoints: false - unitSystem: "METRIC" #IMPERIAL - avoidHighways: false - avoidTolls: false - region: null - travelMode: "DRIVING" #WALKING, BICYCLING - - #//////////////////////////////////////////////////// - #/////////////// Basic Objects ////////////// - #//////////////////////////////////////////////////// - - createPoint : (lat, lng) -> - return new google.maps.Point(lat, lng) - - createLatLng : (lat, lng) -> - return new google.maps.LatLng(lat, lng) - - createLatLngBounds : -> - return new google.maps.LatLngBounds() - - createMap : -> - defaultOptions = - maxZoom: @map_options.maxZoom - minZoom: @map_options.minZoom - zoom: @map_options.zoom - center: @createLatLng(@map_options.center_latitude, @map_options.center_longitude) - mapTypeId: google.maps.MapTypeId[@map_options.type] - mapTypeControl: @map_options.mapTypeControl - disableDefaultUI: @map_options.disableDefaultUI - disableDoubleClickZoom: @map_options.disableDoubleClickZoom - draggable: @map_options.draggable - - mergedOptions = @mergeObjectWithDefault @map_options.raw, defaultOptions - - return new google.maps.Map document.getElementById(@map_options.id), mergedOptions - - - createMarkerImage : (markerPicture, markerSize, origin, anchor, scaledSize) -> - return new google.maps.MarkerImage(markerPicture, markerSize, origin, anchor, scaledSize) - - createSize : (width, height) -> - return new google.maps.Size(width, height) - - #//////////////////////////////////////////////////// - #////////////////////// Markers ///////////////////// - #//////////////////////////////////////////////////// - - createMarker : (args) -> - markerLatLng = @createLatLng(args.Lat, args.Lng) - #Marker sizes are expressed as a Size of X,Y - if args.marker_picture == "" and args.rich_marker == null - defaultOptions = {position: markerLatLng, map: @serviceObject, title: args.marker_title, draggable: args.marker_draggable, zIndex: args.zindex} - mergedOptions = @mergeObjectWithDefault @markers_conf.raw, defaultOptions - return new google.maps.Marker mergedOptions - - if (args.rich_marker != null) - return new RichMarker({ - position: markerLatLng - map: @serviceObject - draggable: args.marker_draggable - content: args.rich_marker - flat: if args.marker_anchor == null then false else args.marker_anchor[1] - anchor: if args.marker_anchor == null then 0 else args.marker_anchor[0] - zIndex: args.zindex - }) - - #default behavior - #calculate MarkerImage anchor location - imageAnchorPosition = @createImageAnchorPosition args.marker_anchor - shadowAnchorPosition = @createImageAnchorPosition args.shadow_anchor - #create or retrieve existing MarkerImages - markerImage = @createOrRetrieveImage(args.marker_picture, args.marker_width, args.marker_height, imageAnchorPosition) - shadowImage = @createOrRetrieveImage(args.shadow_picture, args.shadow_width, args.shadow_height, shadowAnchorPosition) - defaultOptions = {position: markerLatLng, map: @serviceObject, icon: markerImage, title: args.marker_title, draggable: args.marker_draggable, shadow: shadowImage, zIndex: args.zindex} - mergedOptions = @mergeObjectWithDefault @markers_conf.raw, defaultOptions - return new google.maps.Marker mergedOptions - - #checks if obj is included in arr Array and returns the position or false - includeMarkerImage : (arr, obj) -> - for object, index in arr - return index if object.url == obj - return false - - #checks if MarkerImage exists before creating a new one - #returns a MarkerImage or false if ever something wrong is passed as argument - createOrRetrieveImage : (currentMarkerPicture, markerWidth, markerHeight, imageAnchorPosition) -> - return null if (currentMarkerPicture == "" or currentMarkerPicture == null ) - - test_image_index = @includeMarkerImage(@markerImages, currentMarkerPicture) - switch test_image_index - when false - markerImage = @createMarkerImage(currentMarkerPicture, @createSize(markerWidth, markerHeight), null, imageAnchorPosition, null ) - @markerImages.push(markerImage) - return markerImage - break - else - return @markerImages[test_image_index] if typeof test_image_index == 'number' - return false - - #clear markers - clearMarkers : -> - for marker in @markers - @clearMarker marker - - #show and hide markers - showMarkers : -> - for marker in @markers - @showMarker marker - - hideMarkers : -> - for marker in @markers - @hideMarker marker - - clearMarker : (marker) -> - marker.serviceObject.setMap(null) - - showMarker : (marker) -> - marker.serviceObject.setVisible(true) - - hideMarker : (marker) -> - marker.serviceObject.setVisible(false) - - extendBoundsWithMarkers : -> - for marker in @markers - @boundsObject.extend(marker.serviceObject.position) - - #//////////////////////////////////////////////////// - #/////////////////// Clusterer ////////////////////// - #//////////////////////////////////////////////////// - - createClusterer : (markers_array) -> - return new MarkerClusterer( @serviceObject, markers_array, { maxZoom: @markers_conf.clusterer_maxZoom, gridSize: @markers_conf.clusterer_gridSize, styles: @customClusterer() }) - - clearClusterer : -> - @markerClusterer.clearMarkers() - - #creates clusters - clusterize : -> - if @markers_conf.do_clustering == true - #first clear the existing clusterer if any - @clearClusterer() if @markerClusterer != null - - markers_array = new Array - for marker in @markers - markers_array.push(marker.serviceObject) - - @markerClusterer = @createClusterer(markers_array) - - #//////////////////////////////////////////////////// - #/////////////////// INFO WINDOW //////////////////// - #//////////////////////////////////////////////////// - - #// creates infowindows - createInfoWindow : (marker_container) -> - if typeof(@jsTemplate) == "function" or marker_container.description? - marker_container.description = @jsTemplate(marker_container) if typeof(@jsTemplate) == "function" - if @markers_conf.custom_infowindow_class != null - #creating custom infowindow - boxText = document.createElement("div") - boxText.setAttribute("class", @markers_conf.custom_infowindow_class) #to customize - boxText.innerHTML = marker_container.description - marker_container.infowindow = new InfoBox(@infobox(boxText)) - currentMap = this - google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject)) - else - #create default infowindow - marker_container.infowindow = new google.maps.InfoWindow({content: marker_container.description }) - #add the listener associated - currentMap = this - google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject)) - - openInfoWindow : (currentMap, infoWindow, marker) -> - return -> - # Close the latest selected marker before opening the current one. - currentMap.visibleInfoWindow.close() if currentMap.visibleInfoWindow != null - infoWindow.open(currentMap.serviceObject, marker) - currentMap.visibleInfoWindow = infoWindow - - #//////////////////////////////////////////////////// - #///////////////// KML ////////////////// - #//////////////////////////////////////////////////// - - createKmlLayer : (kml) -> - kml_options = kml.options || {} - kml_options = @mergeObjectWithDefault(kml_options, @kml_options) - kml = new google.maps.KmlLayer( kml.url, kml_options) - kml.setMap(@serviceObject) - return kml - - #//////////////////////////////////////////////////// - #/////////////////// POLYLINES ////////////////////// - #//////////////////////////////////////////////////// - - #creates a single polyline, triggered by create_polylines - create_polyline : (polyline) -> - polyline_coordinates = [] - - #2 cases here, either we have a coded array of LatLng or we have an Array of LatLng - for element in polyline - #if we have a coded array - if element.coded_array? - decoded_array = new google.maps.geometry.encoding.decodePath(element.coded_array) - #loop through every point in the array - for point in decoded_array - polyline_coordinates.push(point) - - #or we have an array of latlng - else - #by convention, a single polyline could be customized in the first array or it uses default values - if element == polyline[0] - strokeColor = element.strokeColor || @polylines_conf.strokeColor - strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity - strokeWeight = element.strokeWeight || @polylines_conf.strokeWeight - clickable = element.clickable || @polylines_conf.clickable - zIndex = element.zIndex || @polylines_conf.zIndex - - #add latlng if positions provided - if element.lat? && element.lng? - latlng = @createLatLng(element.lat, element.lng) - polyline_coordinates.push(latlng) - - # Construct the polyline - new_poly = new google.maps.Polyline - path: polyline_coordinates - strokeColor: strokeColor - strokeOpacity: strokeOpacity - strokeWeight: strokeWeight - clickable: clickable - zIndex: zIndex - - #save polyline - polyline.serviceObject = new_poly - new_poly.setMap(@serviceObject) - - - updateBoundsWithPolylines: ()-> - for polyline in @polylines - polyline_points = polyline.serviceObject.latLngs.getArray()[0].getArray() - for point in polyline_points - @boundsObject.extend point - - #//////////////////////////////////////////////////// - #///////////////// KML ////////////////// - #//////////////////////////////////////////////////// - - create_kml : -> - for kml in @kml - kml.serviceObject = @createKmlLayer kml - - #//////////////////////////////////////////////////// - #/////////////////// Other methods ////////////////// - #//////////////////////////////////////////////////// - - fitBounds : -> - @serviceObject.fitBounds(@boundsObject) unless @boundsObject.isEmpty() - - centerMapOnUser : -> - @serviceObject.setCenter(@userLocation) - - updateBoundsWithPolygons: ()-> - for polygon in @polygons - polygon_points = polygon.serviceObject.latLngs.getArray()[0].getArray() - for point in polygon_points - @boundsObject.extend point - - updateBoundsWithCircles: ()-> - for circle in @circles - @boundsObject.extend(circle.serviceObject.getBounds().getNorthEast()) - @boundsObject.extend(circle.serviceObject.getBounds().getSouthWest()) - - extendMapBounds: ()-> - for bound in @map_options.bounds - #create points from bounds provided - @boundsObject.extend @createLatLng(bound.lat, bound.lng) - - adaptMapToBounds:()-> - #if autozoom is false, take user info into account - if !@map_options.auto_zoom - map_center = @boundsObject.getCenter() - @map_options.center_latitude = map_center.lat() - @map_options.center_longitude = map_center.lng() - @serviceObject.setCenter(map_center) - else - @fitBounds() diff --git a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.mapquest.js.coffee b/app/assets/javascripts/search/gmaps4rails/gmaps4rails.mapquest.js.coffee deleted file mode 100644 index 08fca694d0..0000000000 --- a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.mapquest.js.coffee +++ /dev/null @@ -1,145 +0,0 @@ -####################################################################################################### -############################################## Map Quest ############################################# -####################################################################################################### -# http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/api/MQA.Poi.html - -class @Gmaps4RailsMapquest extends Gmaps4Rails - - constructor: -> - super - #Map settings - @map_options = {type: "map"} #map type (map, sat, hyb) - @markers_conf = {} - @mergeWithDefault "markers_conf" - @mergeWithDefault "map_options" - - #//////////////////////////////////////////////////// - #/////////////// Basic Objects ////////////// - #//////////////////////////////////////////////////// - - createPoint: (lat, lng) -> - return new MQA.Poi({lat: lat, lng: lng}) - - createLatLng: (lat, lng) -> - return {lat: lat, lng: lng} - - createLatLngBounds: -> - - createMap: -> - map = new MQA.TileMap( #// Constructs an instance of MQA.TileMap - document.getElementById(@map_options.id), #//the id of the element on the page you want the map to be added into - @map_options.zoom, #//intial zoom level of the map - {lat: @map_options.center_latitude, lng: @map_options.center_longitude}, - @map_options.type) #//map type (map, sat, hyb) - - MQA.withModule('zoomcontrol3', (-> - map.addControl( - new MQA.LargeZoomControl3(), - new MQA.MapCornerPlacement(MQA.MapCorner.TOP_LEFT) - ) - )) - return map - - createMarkerImage: (markerPicture, markerSize, origin, anchor, scaledSize) -> - - #//////////////////////////////////////////////////// - #////////////////////// Markers ///////////////////// - #//////////////////////////////////////////////////// - - createMarker: (args)-> - marker = new MQA.Poi( {lat: args.Lat, lng: args.Lng} ) - - if args.marker_picture != "" - icon = new MQA.Icon(args.marker_picture, args.marker_height, args.marker_width) - marker.setIcon(icon) - if args.marker_anchor != null - marker.setBias({x: args.marker_anchor[0], y: args.marker_anchor[1]}) - - if args.shadow_picture != "" - icon = new MQA.Icon(args.shadow_picture, args.shadow_height, args.shadow_width) - marker.setShadow(icon) - - if args.shadow_anchor != null - marker.setShadowOffset({x: args.shadow_anchor[0], y: args.shadow_anchor[1]}) - - @addToMap marker - return marker - - - #// clear markers - clearMarkers: -> - for marker in markers - @clearMarker marker - - #//show and hide markers - showMarkers: -> - for marker in markers - @showMarker marker - - hideMarkers: -> - for marker in markers - @hideMarker marker - - clearMarker: (marker) -> - @removeFromMap(marker.serviceObject) - - showMarker: (marker) -> - #// marker.serviceObject - - hideMarker: (marker) -> - #// marker.serviceObject - - extendBoundsWithMarkers: -> - if @markers.length >=2 - @boundsObject = new MQA.RectLL(@markers[0].serviceObject.latLng, @markers[1].serviceObject.latLng) - for marker in @markers - @boundsObject.extend marker.serviceObject.latLng - - #//////////////////////////////////////////////////// - #/////////////////// Clusterer ////////////////////// - #//////////////////////////////////////////////////// - - createClusterer: (markers_array) -> - - clearClusterer: -> - - #//creates clusters - clusterize: -> - - #//////////////////////////////////////////////////// - #/////////////////// INFO WINDOW //////////////////// - #//////////////////////////////////////////////////// - - #// creates infowindows - createInfoWindow: (marker_container) -> - marker_container.serviceObject.setInfoTitleHTML(marker_container.description) - #//TODO: how to disable the mouseover display when using setInfoContentHTML? - #//marker_container.serviceObject.setInfoContentHTML(marker_container.description); - - #//////////////////////////////////////////////////// - #/////////////////// Other methods ////////////////// - #//////////////////////////////////////////////////// - - fitBounds: -> - @serviceObject.zoomToRect @boundsObject if @markers.length >=2 - @serviceObject.setCenter @markers[0].serviceObject.latLng if @markers.length == 1 - - centerMapOnUser: -> - @serviceObject.setCenter @userLocation - - addToMap: (object) -> - @serviceObject.addShape object - - removeFromMap: (object)-> - @serviceObject.removeShape object - - updateBoundsWithPolylines: ()-> - - updateBoundsWithPolygons: ()-> - - updateBoundsWithCircles: ()-> - - extendMapBounds :-> - - adaptMapToBounds: -> - @fitBounds() \ No newline at end of file diff --git a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.openlayers.js.coffee b/app/assets/javascripts/search/gmaps4rails/gmaps4rails.openlayers.js.coffee deleted file mode 100644 index 1cddc04e39..0000000000 --- a/app/assets/javascripts/search/gmaps4rails/gmaps4rails.openlayers.js.coffee +++ /dev/null @@ -1,261 +0,0 @@ -####################################################################################################### -############################################## Open Layers ########################################## -####################################################################################################### - -#// http://wiki.openstreetmap.org/wiki/OpenLayers -#// http://openlayers.org/dev/examples -#//http://docs.openlayers.org/contents.html - -class @Gmaps4RailsOpenlayers extends Gmaps4Rails - - constructor: -> - super - @map_options = {} - @mergeWithDefault "map_options" - @markers_conf = {} - @mergeWithDefault "markers_conf" - - @openMarkers = null - @markersLayer = null - @markersControl = null - @polylinesLayer = null - - #//////////////////////////////////////////////////// - #/////////////// Basic Objects //////////////////// - #//////////////////////////////////////////////////// - - createPoint: (lat, lng)-> - - createLatLng: (lat, lng)-> - return new OpenLayers.LonLat(lng, lat).transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")) # transform from WGS 1984 to Spherical Mercator Projection - - createAnchor: (offset)-> - return null if offset == null - return new OpenLayers.Pixel(offset[0], offset[1]) - - createSize: (width, height)-> - return new OpenLayers.Size(width, height) - - createLatLngBounds: -> - return new OpenLayers.Bounds() - - createMap: -> - #//todo add customization: kind of map and other map options - map = new OpenLayers.Map(@map_options.id) - map.addLayer(new OpenLayers.Layer.OSM()) - map.setCenter(@createLatLng(@map_options.center_latitude, @map_options.center_longitude), #// Center of the map - @map_options.zoom) #// Zoom level - return map - - #//////////////////////////////////////////////////// - #////////////////////// Markers ///////////////////// - #//////////////////////////////////////////////////// - #//http://openlayers.org/dev/examples/marker-shadow.html - createMarker: (args) -> - style_mark = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']) - style_mark.fillOpacity = 1 - - #//creating markers' dedicated layer - if (@markersLayer == null) - @markersLayer = new OpenLayers.Layer.Vector("Markers", null) - @serviceObject.addLayer(@markersLayer) - #//TODO move? - @markersLayer.events.register("featureselected", @markersLayer, @onFeatureSelect) - @markersLayer.events.register("featureunselected", @markersLayer, @onFeatureUnselect) - @markersControl = new OpenLayers.Control.SelectFeature(@markersLayer) - @serviceObject.addControl(@markersControl) - @markersControl.activate() - #//showing default pic if none available - if args.marker_picture == "" - #style_mark.graphicWidth = 24 - style_mark.graphicHeight = 30 - style_mark.externalGraphic = "http://openlayers.org/dev/img/marker-blue.png" - #//creating custom pic - else - style_mark.graphicWidth = args.marker_width - style_mark.graphicHeight = args.marker_height - style_mark.externalGraphic = args.marker_picture - #//adding anchor if any - if args.marker_anchor != null - style_mark.graphicXOffset = args.marker_anchor[0] - style_mark.graphicYOffset = args.marker_anchor[1] - #//adding shadow if any - if args.shadow_picture != "" - style_mark.backgroundGraphic = args.shadow_picture - style_mark.backgroundWidth = args.shadow_width - style_mark.backgroundHeight = args.shadow_height - #//adding shadow's anchor if any - if args.shadow_anchor != null - style_mark.backgroundXOffset = args.shadow_anchor[0] - style_mark.backgroundYOffset = args.shadow_anchor[1] - - style_mark.graphicTitle = args.marker_title - marker = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Point(args.Lng, args.Lat), - null, - style_mark) - #//changing coordinates so that it actually appears on the map! - marker.geometry.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")) - #//adding layer to the map - @markersLayer.addFeatures([marker]) - - return marker - - #//clear markers - clearMarkers: -> - @clearMarkersLayerIfExists() - @markersLayer = null - @boundsObject = new OpenLayers.Bounds() - - clearMarkersLayerIfExists: -> - @serviceObject.removeLayer(@markersLayer) if @markersLayer != null and @serviceObject.getLayer(@markersLayer.id) != null - - extendBoundsWithMarkers: -> - console.log "here" - for marker in @markers - @boundsObject.extend(@createLatLng(marker.lat,marker.lng)) - - #//////////////////////////////////////////////////// - #/////////////////// Clusterer ////////////////////// - #//////////////////////////////////////////////////// - #//too ugly to be considered valid :( - - createClusterer: (markers_array)-> - options = - pointRadius: "${radius}" - fillColor: "#ffcc66" - fillOpacity: 0.8 - strokeColor: "#cc6633" - strokeWidth: "${width}" - strokeOpacity: 0.8 - funcs = - context: - width: (feature) -> - return (feature.cluster) ? 2 : 1 - radius: (feature) -> - pix = 2 - pix = Math.min(feature.attributes.count, 7) + 2 if feature.cluster - return pix - - style = new OpenLayers.Style options, funcs - - strategy = new OpenLayers.Strategy.Cluster() - - clusters = new OpenLayers.Layer.Vector "Clusters", - strategies: [strategy] - styleMap: new OpenLayers.StyleMap - "default": style - "select": - fillColor: "#8aeeef" - strokeColor: "#32a8a9" - - @clearMarkersLayerIfExists() - @serviceObject.addLayer(clusters) - clusters.addFeatures(markers_array) - return clusters - - clusterize: -> - - if @markers_conf.do_clustering == true - #//first clear the existing clusterer if any - if @markerClusterer != null - @clearClusterer() - markers_array = new Array - for marker in @markers - markers_array.push(marker.serviceObject) - @markerClusterer = @createClusterer markers_array - - clearClusterer: -> - @serviceObject.removeLayer @markerClusterer - - #//////////////////////////////////////////////////// - #/////////////////// INFO WINDOW //////////////////// - #//////////////////////////////////////////////////// - - #// creates infowindows - createInfoWindow: (marker_container) -> - marker_container.serviceObject.infoWindow = marker_container.description if marker_container.description? - - onPopupClose: (evt) -> - #// 'this' is the popup. - @markersControl.unselect @feature - - onFeatureSelect: (evt) -> - feature = evt.feature - popup = new OpenLayers.Popup.FramedCloud("featurePopup", - feature.geometry.getBounds().getCenterLonLat(), - new OpenLayers.Size(300,200), - feature.infoWindow, - null, true, @onPopupClose) - feature.popup = popup - popup.feature = feature - @map.addPopup popup - - onFeatureUnselect: (evt) -> - feature = evt.feature - if feature.popup - #//popup.feature = null; - @map.removePopup feature.popup - feature.popup.destroy() - feature.popup = null - - #//////////////////////////////////////////////////// - #/////////////////// POLYLINES ////////////////////// - #//////////////////////////////////////////////////// - - create_polyline : (polyline) -> - - if(@polylinesLayer == null) - @polylinesLayer = new OpenLayers.Layer.Vector("Polylines", null) - @serviceObject.addLayer(@polylinesLayer) - @polylinesLayer.events.register("featureselected", @polylinesLayer, @onFeatureSelect) - @polylinesLayer.events.register("featureunselected", @polylinesLayer, @onFeatureUnselect) - @polylinesControl = new OpenLayers.Control.DrawFeature(@polylinesLayer, OpenLayers.Handler.Path) - @serviceObject.addControl(@polylinesControl) - - polyline_coordinates = [] - - for element in polyline - #by convention, a single polyline could be customized in the first array or it uses default values - if element == polyline[0] - strokeColor = element.strokeColor || @polylines_conf.strokeColor - strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity - strokeWeight = element.strokeWeight || @polylines_conf.strokeWeight - clickable = element.clickable || @polylines_conf.clickable - zIndex = element.zIndex || @polylines_conf.zIndex - - #add latlng if positions provided - if element.lat? && element.lng? - latlng = new OpenLayers.Geometry.Point(element.lng, element.lat) - polyline_coordinates.push(latlng) - - line_points = new OpenLayers.Geometry.LineString(polyline_coordinates); - line_style = { strokeColor: strokeColor, strokeOpacity: strokeOpacity, strokeWidth: strokeWeight }; - - polyline = new OpenLayers.Feature.Vector(line_points, null, line_style); - polyline.geometry.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")) - - @polylinesLayer.addFeatures([polyline]) - - return polyline - - updateBoundsWithPolylines: ()-> - - updateBoundsWithPolygons: ()-> - - updateBoundsWithCircles: ()-> - - # #//////////////////////////////////////////////////// - # #/////////////////// Other methods ////////////////// - # #//////////////////////////////////////////////////// - - fitBounds: -> - @serviceObject.zoomToExtent(@boundsObject, true) - - centerMapOnUser: -> - @serviceObject.setCenter @userLocation - - extendMapBounds :-> - - adaptMapToBounds: -> - @fitBounds() diff --git a/app/assets/javascripts/search/jquery.backstretch.js b/app/assets/javascripts/search/jquery.backstretch.js deleted file mode 100644 index 4cb7175e99..0000000000 --- a/app/assets/javascripts/search/jquery.backstretch.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! Backstretch - v2.0.4 - 2013-06-19 - * http://srobbin.com/jquery-plugins/backstretch/ - * Copyright (c) 2013 Scott Robbin; Licensed MIT */ -(function(a,d,p){a.fn.backstretch=function(c,b){(c===p||0===c.length)&&a.error("No images were supplied for Backstretch");0===a(d).scrollTop()&&d.scrollTo(0,0);return this.each(function(){var d=a(this),g=d.data("backstretch");if(g){if("string"==typeof c&&"function"==typeof g[c]){g[c](b);return}b=a.extend(g.options,b);g.destroy(!0)}g=new q(this,c,b);d.data("backstretch",g)})};a.backstretch=function(c,b){return a("body").backstretch(c,b).data("backstretch")};a.expr[":"].backstretch=function(c){return a(c).data("backstretch")!==p};a.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5E3,fade:0};var r={left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},s={position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxHeight:"none",maxWidth:"none",zIndex:-999999},q=function(c,b,e){this.options=a.extend({},a.fn.backstretch.defaults,e||{});this.images=a.isArray(b)?b:[b];a.each(this.images,function(){a("")[0].src=this});this.isBody=c===document.body;this.$container=a(c);this.$root=this.isBody?l?a(d):a(document):this.$container;c=this.$container.children(".backstretch").first();this.$wrap=c.length?c:a('
').css(r).appendTo(this.$container);this.isBody||(c=this.$container.css("position"),b=this.$container.css("zIndex"),this.$container.css({position:"static"===c?"relative":c,zIndex:"auto"===b?0:b,background:"none"}),this.$wrap.css({zIndex:-999998}));this.$wrap.css({position:this.isBody&&l?"fixed":"absolute"});this.index=0;this.show(this.index);a(d).on("resize.backstretch",a.proxy(this.resize,this)).on("orientationchange.backstretch",a.proxy(function(){this.isBody&&0===d.pageYOffset&&(d.scrollTo(0,1),this.resize())},this))};q.prototype={resize:function(){try{var a={left:0,top:0},b=this.isBody?this.$root.width():this.$root.innerWidth(),e=b,g=this.isBody?d.innerHeight?d.innerHeight:this.$root.height():this.$root.innerHeight(),j=e/this.$img.data("ratio"),f;j>=g?(f=(j-g)/2,this.options.centeredY&&(a.top="-"+f+"px")):(j=g,e=j*this.$img.data("ratio"),f=(e-b)/2,this.options.centeredX&&(a.left="-"+f+"px"));this.$wrap.css({width:b,height:g}).find("img:not(.deleteable)").css({width:e,height:j}).css(a)}catch(h){}return this},show:function(c){if(!(Math.abs(c)>this.images.length-1)){var b=this,e=b.$wrap.find("img").addClass("deleteable"),d={relatedTarget:b.$container[0]};b.$container.trigger(a.Event("backstretch.before",d),[b,c]);this.index=c;clearInterval(b.interval);b.$img=a("").css(s).bind("load",function(f){var h=this.width||a(f.target).width();f=this.height||a(f.target).height();a(this).data("ratio",h/f);a(this).fadeIn(b.options.speed||b.options.fade,function(){e.remove();b.paused||b.cycle();a(["after","show"]).each(function(){b.$container.trigger(a.Event("backstretch."+this,d),[b,c])})});b.resize()}).appendTo(b.$wrap);b.$img.attr("src",b.images[c]);return b}},next:function(){return this.show(this.indexe||d.operamini&&"[object OperaMini]"==={}.toString.call(d.operamini)||n&&7458>t||-1e||h&&6>h||"palmGetResource"in d&&e&&534>e||-1=k)})(jQuery,window); \ No newline at end of file diff --git a/app/assets/javascripts/search/jquery.offcanvas.js b/app/assets/javascripts/search/jquery.offcanvas.js deleted file mode 100644 index 4f65c081b9..0000000000 --- a/app/assets/javascripts/search/jquery.offcanvas.js +++ /dev/null @@ -1,62 +0,0 @@ -//;(function (window, document, $) { -// alert($("#sidebarButton").html()); -//}(this, document, jQuery)); - - - - -//;(function (window, document, $) { -// // Set the negative margin on the top menu for slide-menu pages -// var $selector1 = $('#topMenu'), -// events = 'click.fndtn'; -// if ($selector1.length > 0) $selector1.css("margin-top", $selector1.height() * -1); -// -// // Watch for clicks to show the sidebar -// var $selector2 = $('#sidebarButton'); -// if ($selector2.length > 0) { -// $('#sidebarButton').on(events, function (e) { -// console.log("testing one two three"); -// e.preventDefault(); -// $('body').toggleClass('active'); -// }); -// } -// else { -// console.log("not supposed to be there"); -// } -// -// // Watch for clicks to show the menu for slide-menu pages -// var $selector3 = $('#menuButton'); -// if ($selector3.length > 0) { -// $('#menuButton').on(events, function (e) { -// e.preventDefault(); -// $('body').toggleClass('active-menu'); -// }); -// } -// -// // // Adjust sidebars and sizes when resized -// // $(window).resize(function() { -// // // if (!navigator.userAgent.match(/Android/i)) $('body').removeClass('active'); -// // var $selector4 = $('#topMenu'); -// // if ($selector4.length > 0) $selector4.css("margin-top", $selector4.height() * -1); -// // }); -// -// // Switch panels for the paneled nav on mobile -// var $selector5 = $('#switchPanels'); -// if ($selector5.length > 0) { -// $('#switchPanels dd').on(events, function (e) { -// e.preventDefault(); -// var switchToPanel = $(this).children('a').attr('href'), -// switchToIndex = $(switchToPanel).index(); -// $(this).toggleClass('active').siblings().removeClass('active'); -// $(switchToPanel).parent().css("left", (switchToIndex * (-100) + '%')); -// }); -// } -// -// $('#nav li a').on(events, function (e) { -// alert("test"); -// e.preventDefault(); -// var href = $(this).attr('href'), -// $target = $(href); -// $('html, body').animate({scrollTop : $target.offset().top}, 300); -// }); -//}(this, document, jQuery)); diff --git a/app/assets/javascripts/search/modernizr.foundation.js b/app/assets/javascripts/search/modernizr.foundation.js deleted file mode 100644 index 4eb3d0655f..0000000000 --- a/app/assets/javascripts/search/modernizr.foundation.js +++ /dev/null @@ -1,4 +0,0 @@ -/* Modernizr 2.6.2 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load - */ -;window.Modernizr=function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={svg:"http://www.w3.org/2000/svg"},o={},p={},q={},r=[],s=r.slice,t,u=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f - $('#cart_adjustments').hide() - - $('th.cart-adjustment-header').html('Distribution...') - $('th.cart-adjustment-header a').click -> - $('#cart_adjustments').toggle() - $('th.cart-adjustment-header a').html('Distribution') - false diff --git a/app/assets/javascripts/store/controllers/cart.js.coffee b/app/assets/javascripts/store/controllers/cart.js.coffee deleted file mode 100644 index cc7538249e..0000000000 --- a/app/assets/javascripts/store/controllers/cart.js.coffee +++ /dev/null @@ -1,20 +0,0 @@ -'use strict' - -angular.module('store', ['ngResource']). - controller('CartCtrl', ['$scope', '$window', 'CartFactory', ($scope, $window, CartFactory) -> - - $scope.state = 'Empty' - - $scope.loadCart = (cart_id) -> - if cart_id? - CartFactory.load cart_id, (cart) -> - $scope.cart = cart - if $scope.cart?.orders?.length > 0 - $scope.state = "There's something there...." - - $scope.addVariant = (variant, quantity) -> - - ]) - .config(['$httpProvider', ($httpProvider) -> - $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') - ]) diff --git a/app/assets/javascripts/store/factories/cart.js.coffee b/app/assets/javascripts/store/factories/cart.js.coffee deleted file mode 100644 index e327fc336d..0000000000 --- a/app/assets/javascripts/store/factories/cart.js.coffee +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -angular.module('store'). - factory('CartFactory', ['$resource', '$window', '$http', ($resource, $window, $http) -> - Cart = $resource '/open_food_network/cart/:cart_id.json', {}, - { 'show': { method: 'GET'} } - - load: (id, callback) -> - Cart.show {cart_id: id}, (cart) -> - callback(cart) - ]) diff --git a/app/assets/javascripts/store/products.js b/app/assets/javascripts/store/products.js deleted file mode 100644 index 127e8ce28f..0000000000 --- a/app/assets/javascripts/store/products.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Update the price on the product details page in real time when the variant or the quantity are changed. - **/ - -$(document).ready(function() { - // Product page with variant choice - $("#product-variants input[type='radio']").change(products_update_price_with_variant); - $("#quantity").change(products_update_price_with_variant); - $("#quantity").change(); - - // Product page with master price only - $(".add-to-cart input.title:not(#quantity):not(.max_quantity)").change(products_update_price_without_variant).change(); - - // Product page other - $("#distributor_id").change(function() { - var distributor_html = distributors[$(this).val()]; - if(!distributor_html) { - distributor_html = 'When you select a distributor for your order, their address and pickup times will be displayed here.'; - } - $("#product-distributor-details .distributor-details").html(distributor_html); - }); -}); - - -function products_update_price_with_variant() { - var variant_price = $("#product-variants input[type='radio']:checked").parent().find("span.price").html().trim(); - variant_price = variant_price.substr(2, variant_price.length-3); - - var quantity = $("#quantity").val(); - - $("#product-price span.price").html("$"+(parseFloat(variant_price) * parseInt(quantity)).toFixed(2)); -} - - -function products_update_price_without_variant() { - var master_price = $("#product-price span.price").data('master-price'); - if(master_price == null) { - // Store off the master price - master_price = $("#product-price span.price").html(); - master_price = master_price.substring(1); - $("#product-price span.price").data('master-price', master_price); - } - - var quantity = $(this).val(); - - $("#product-price span.price").html("$"+(parseFloat(master_price)*parseInt(quantity)).toFixed(2)); -} diff --git a/app/assets/javascripts/store/shop_front.js.coffee b/app/assets/javascripts/store/shop_front.js.coffee deleted file mode 100644 index 1a7044732f..0000000000 --- a/app/assets/javascripts/store/shop_front.js.coffee +++ /dev/null @@ -1,4 +0,0 @@ -$(document).ready -> - $("#order_order_cycle_id").change -> $("#order_cycle_select").submit() - $("#reset_order_cycle").click -> return false unless confirm "Changing your collection date will clear your cart." - $(".shop-distributor.empties-cart").click -> return false unless confirm "Changing your location will clear your cart." diff --git a/app/assets/javascripts/templates/hub_modal.html.haml b/app/assets/javascripts/templates/enterprise_modal.html.haml similarity index 100% rename from app/assets/javascripts/templates/hub_modal.html.haml rename to app/assets/javascripts/templates/enterprise_modal.html.haml diff --git a/app/assets/javascripts/templates/map_modal_producer.html.haml b/app/assets/javascripts/templates/map_modal_producer.html.haml deleted file mode 100644 index dff26519d3..0000000000 --- a/app/assets/javascripts/templates/map_modal_producer.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ng-include{src: "'partials/enterprise_header.html'"} -%ng-include{src: "'partials/enterprise_details.html'"} -%ng-include{src: "'partials/hub_actions.html'"} -%ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml index 96a4fb207e..27a57ed90b 100644 --- a/app/assets/javascripts/templates/partials/enterprise_header.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml @@ -1,12 +1,13 @@ -.highlight - .highlight-top - %p.right - {{ [enterprise.address.city, enterprise.address.state_name] | printArray}} - %h3{"ng-if" => "enterprise.is_distributor"} - %a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true} - %i.ofn-i_040-hub +.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-empties-cart" => "enterprise", bindonce: true} + %i{"ng-class" => "enterprise.icon_font"} + %span {{ enterprise.name }} + %h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"} + %i{"ng-class" => "enterprise.icon_font"} %span {{ enterprise.name }} - %h3{"ng-if" => "!enterprise.is_distributor"} - %i.ofn-i_036-producers - %span {{ enterprise.name }} + .small-12.medium-5.large-4.columns.text-right.small-only-text-left + %p {{ [enterprise.address.city, enterprise.address.state_name] | printArray}} %img.hero-img{"ng-src" => "{{enterprise.promo_image}}"} diff --git a/app/assets/javascripts/templates/partials/hub_actions.html.haml b/app/assets/javascripts/templates/partials/hub_actions.html.haml index fa9ff4e183..61e74afb42 100644 --- a/app/assets/javascripts/templates/partials/hub_actions.html.haml +++ b/app/assets/javascripts/templates/partials/hub_actions.html.haml @@ -1,4 +1,4 @@ -.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.has_shopfront"} +.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0 && enterprise.is_distributor"} .cta-container.small-12.columns %label Shop for diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index 27933cefcb..815200821a 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -1,4 +1,4 @@ -.row.pad-top{bindonce: true, ng: { if: 'enterprise.has_shopfront' } } +.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } } .cta-container.small-12.columns .row .small-4.columns diff --git a/app/assets/javascripts/templates/price_breakdown.html.haml b/app/assets/javascripts/templates/price_breakdown.html.haml index 0b7eba917a..cf82e31457 100644 --- a/app/assets/javascripts/templates/price_breakdown.html.haml +++ b/app/assets/javascripts/templates/price_breakdown.html.haml @@ -10,26 +10,26 @@ .expanded{"ng-show" => "expanded"} %ul %li.cost - .right {{ variant.base_price | currency }} + .right {{ variant.price | localizeCurrency }} Item cost %li{"bo-if" => "variant.fees.admin"} - .right {{ variant.fees.admin | currency }} + .right {{ variant.fees.admin | localizeCurrency }} Admin fee %li{"bo-if" => "variant.fees.sales"} - .right {{ variant.fees.sales | currency }} + .right {{ variant.fees.sales | localizeCurrency }} Sales fee %li{"bo-if" => "variant.fees.packing"} - .right {{ variant.fees.packing | currency }} + .right {{ variant.fees.packing | localizeCurrency }} Packing fee %li{"bo-if" => "variant.fees.transport"} - .right {{ variant.fees.transport | currency }} + .right {{ variant.fees.transport | localizeCurrency }} Transport fee %li{"bo-if" => "variant.fees.fundraising"} - .right {{ variant.fees.fundraising | currency }} + .right {{ variant.fees.fundraising | localizeCurrency }} Fundraising fee %li %strong - .right = {{ variant.price | currency }} + .right = {{ variant.price_with_fees | localizeCurrency }}   %a{"ng-click" => "expanded = !expanded"} diff --git a/app/assets/javascripts/templates/producer_modal.html.haml b/app/assets/javascripts/templates/producer_modal.html.haml deleted file mode 100644 index db6f927e21..0000000000 --- a/app/assets/javascripts/templates/producer_modal.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -%ng-include{src: "'partials/enterprise_header.html'"} -%ng-include{src: "'partials/enterprise_details.html'"} -%ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/registration/about.html.haml b/app/assets/javascripts/templates/registration/about.html.haml index 07e631345f..5b7d2e6df6 100644 --- a/app/assets/javascripts/templates/registration/about.html.haml +++ b/app/assets/javascripts/templates/registration/about.html.haml @@ -1,44 +1,47 @@ .container#registration-about - .header - %h2 Nice one! - %h5 - Now let's flesh out the details about - %span.brick{"ng-show" => "enterprise.is_distributor"} - {{ enterprise.name }} - %span.turquoise{"ng-show" => "!enterprise.is_distributor" } - {{ enterprise.name }} - %ng-include{ src: "'registration/steps.html'" } - %form{ name: 'about', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('social',about)" } } + .row + .small-12.columns + %header + %h2 Nice one! + %h5 + Now let's flesh out the details about + %span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } } + {{ enterprise.name }} + + %form{ name: 'about', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('images',about)" } } .row .small-12.columns - .alert-box.alert{"data-alert" => ""} - {{ enterprise.name }} won't be visible on the Open Food Network until you enter a long and short description. - %a.close{:href => "#"} × - - .alert-box.info{"data-alert" => ""} - {{ enterprise.name }} has been created on the Open Food Network. If you leave at any point from here onwards, your enterprise will be saved, and you can always login to the admin section to update or continue filling out your enterprise details. - %a.close{:href => "#"} × + .alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } } + %h6 Success! {{ enterprise.name }} added to the Open Food Network + %span If you exit the wizard at any stage, login and go to admin to edit or update your enterprise details. + %a.close{ ng: { click: "close()" } } × .small-12.large-8.columns .row .small-12.columns - %label{ for: 'enterprise_description' } Short Description: - %input.chunky.small-12.columns{ id: 'enterprise_description', placeholder: "A short sentence describing your enterprise", ng: { model: 'enterprise.description' } } + .field + %label{ for: 'enterprise_description' } Short Description: + %input.chunky{ id: 'enterprise_description', placeholder: "A short sentence describing your enterprise", ng: { model: 'enterprise.description' } } .row .small-12.columns - %label{ for: 'enterprise_long_desc' } Long Description: - %textarea.chunky.small-12.columns{ id: 'enterprise_long_desc', placeholder: "We recommend keeping your description to under 600 characters or 150 words. Why? Cus people are lazy, and don't like to read too much text online. ;)", ng: { model: 'enterprise.long_description' } } - %small {{ enterprise.long_description.length }} characters used + .field + %label{ for: 'enterprise_long_desc' } Long Description: + %textarea.chunky{ id: 'enterprise_long_desc', rows: 6, placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words.", ng: { model: 'enterprise.long_description' } } + %small {{ enterprise.long_description.length }} characters / up to 600 recommended .small-12.large-4.columns .row .small-12.columns - %label{ for: 'enterprise_abn' } ABN: - %input.chunky.small-12.columns{ id: 'enterprise_abn', placeholder: "eg. 99 123 456 789", ng: { model: 'enterprise.abn' } } + .field + %label{ for: 'enterprise_abn' } ABN: + %input.chunky{ id: 'enterprise_abn', placeholder: "eg. 99 123 456 789", ng: { model: 'enterprise.abn' } } .row .small-12.columns - %label{ for: 'enterprise_acn' } ACN: - %input.chunky.small-12.columns{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } } + .field + %label{ for: 'enterprise_acn' } ACN: + %input.chunky{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } } + .row.buttons.pad-top .small-12.columns - %input.button.primary{ type: "submit", value: "Continue" } + %input.button.primary.right{ type: "submit", value: "Continue" } + diff --git a/app/assets/javascripts/templates/registration/address.html.haml b/app/assets/javascripts/templates/registration/address.html.haml deleted file mode 100644 index 6fe39e9285..0000000000 --- a/app/assets/javascripts/templates/registration/address.html.haml +++ /dev/null @@ -1,60 +0,0 @@ -.container#registration-address - .header - %h2 - Greetings - %span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } } - {{ enterprise.name }} - - %h5 Now we need to know where you are - %ng-include{ src: "'registration/steps.html'" } - %form{ name: 'address', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',address)" } } - .row.content - .small-12.medium-12.large-7.columns - .row - .small-12.columns.field - %label{ for: 'enterprise_address' } Address: - %input.chunky.small-12.columns{ id: 'enterprise_address', name: 'address1', required: true, placeholder: "eg. 123 Cranberry Drive", required: true, ng: { model: 'enterprise.address.address1' } } - %span.error.small-12.columns{ ng: { show: "address.address1.$error.required && submitted" } } - You need to enter an address. - .row - .small-12.large-8.columns.field - %label{ for: 'enterprise_city' } Suburb: - %input.chunky.small-12.columns{ id: 'enterprise_city', name: 'city', required: true, placeholder: "eg. Northcote", ng: { model: 'enterprise.address.city' } } - %span.error.small-12.columns{ ng: { show: "address.city.$error.required && submitted" } } - You need to enter a suburb. - .small-12.large-4.columns.field - %label{ for: 'enterprise_zipcode' } Postcode: - %input.chunky.small-12.columns{ id: 'enterprise_zipcode', name: 'zipcode', required: true, placeholder: "eg. 3070", ng: { model: 'enterprise.address.zipcode' } } - %span.error.small-12.columns{ ng: { show: "address.zipcode.$error.required && submitted" } } - You need to enter a postcode. - .row - .small-12.large-8.columns.field - %label{ for: 'enterprise_country' } Country: - %select.chunky.small-12.columns{ id: 'enterprise_country', name: 'country', required: true, ng: { model: 'enterprise.country', options: 'c as c.name for c in countries' } } - %span.error.small-12.columns{ ng: { show: "address.country.$error.required && submitted" } } - You need to enter a country. - .small-12.large-4.columns.field - %label{ for: 'enterprise_state' } State: - %select.chunky.small-12.columns{ id: 'enterprise_state', name: 'state', ng: { model: 'enterprise.address.state_id', options: 's.id as s.abbr for s in enterprise.country.states', show: 'countryHasStates()', required: 'countryHasStates()' } } - %span.error.small-12.columns{ ng: { show: "address.state.$error.required && submitted" } } - You need to enter a state. - .small-12.medium-12.large-5.hide-for-small-only - // This is the location area - / %h6 - / Location display - / %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your enterprise's address on the Open Food Network. By default, full location is shown everywhere including street name and number."} - / .row - / .small-12.columns - / %label.indent-checkbox - / %input{ type: 'checkbox', id: 'enterpise_suburb_only', ng: { model: 'enterprise.suburb_only' } } - / Hide my street name and street number from the public (ie. only show the suburb) - / .small-12.columns - / %label.indent-checkbox - / %input{ type: 'checkbox', id: 'enterprise_on_map', ng: { model: 'enterprise.on_map' } } - / Blur my location on the map (show an approximate, not exact pin) - - .row.buttons - .small-12.columns - %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('details')" } } -   - %input.button.primary{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/contact.html.haml b/app/assets/javascripts/templates/registration/contact.html.haml index c7262248d0..916a614c0b 100644 --- a/app/assets/javascripts/templates/registration/contact.html.haml +++ b/app/assets/javascripts/templates/registration/contact.html.haml @@ -1,12 +1,13 @@ .container#registration-contact - .header - %h2 Last step to create your enterprise! - %h5 - Who is responsible for managing - %span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } } - {{ enterprise.name }}? %ng-include{ src: "'registration/steps.html'" } - %form{ name: 'contact', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(contact)" } } + .row + .small-12.columns + %header + %h2 Greetings! + %h5 + Who is responsible for managing {{ enterprise.name }}? + + %form{ name: 'contact', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('type',contact)" } } .row.content .small-12.medium-12.large-7.columns .row @@ -39,8 +40,8 @@ / .small-12.columns / %label.indent-checkbox / %input{ type: 'checkbox', id: 'contact_phone_profile', ng: { model: 'enterprise.phone_in_profile' } }   Display phone in profile + .row.buttons .small-12.columns - %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('address')" } } -   - %input.button.primary{ type: "submit", value: "Continue" } + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('details')" } } + %input.button.primary.right{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/details.html.haml b/app/assets/javascripts/templates/registration/details.html.haml index bb358a1864..f1c0503f78 100644 --- a/app/assets/javascripts/templates/registration/details.html.haml +++ b/app/assets/javascripts/templates/registration/details.html.haml @@ -1,42 +1,77 @@ .container#registration-details{bindonce: true} - .header - %h2 Let's Get Started - %h5{ bo: { if: "enterprise.type != 'single'" } } Woot! First we need to know what sort of enterprise you are: - %h5{ bo: { if: "enterprise.type == 'single'" } } Woot! First we need to know the name of your farm: %ng-include{ src: "'registration/steps.html'" } - %form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('address',details)" } } - .row - .small-12.columns.field - %label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'single'" } } Enterprise Name: - %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'single'" } } Farm Name: - %input.chunky.small-12.columns{ id: 'enterprise_name', name: 'name', placeholder: "eg. Charlie's Awesome Farm", required: true, ng: { model: 'enterprise.name' } } - %span.error.small-12.columns{ ng: { show: "details.name.$error.required && submitted" } } - You need to enter a name for your enterprise! + .row + .small-12.columns + %header + %h2 Let's Get Started + %h5{ bo: { if: "enterprise.type != 'own'" } } Woot! First we need to know a little bit about your enterprise: + %h5{ bo: { if: "enterprise.type == 'own'" } } Woot! First we need to know a little bit about your farm: - .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'single'" } } - .small-12.columns.field + %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'" } } Enterprise Name: + %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } Farm Name: + %input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "e.g. Charlie's Awesome Farm", required: true, ng: { model: 'enterprise.name' } } + %span.error{ ng: { show: "details.name.$error.required && submitted" } } + Please choose a unique name for your enterprise + + .row + .small-12.medium-9.large-6.columns + .field + %label{ for: 'enterprise_address' } Address line 1: + %input.chunky{ id: 'enterprise_address', name: 'address1', required: true, placeholder: "e.g. 123 Cranberry Drive", required: true, ng: { model: 'enterprise.address.address1' } } + %span.error{ ng: { show: "details.address1.$error.required && submitted" } } + Please enter an address + .field + %label{ for: 'enterprise_address2' } Address line 2: + %input.chunky{ id: 'enterprise_address2', name: 'address2', required: false, placeholder: "", required: false, ng: { model: 'enterprise.address.address2' } } + + .small-12.medium-9.large-6.columns.end .row - .small-12.columns - %label Choose one: + .small-12.medium-8.large-8.columns + .field + %label{ for: 'enterprise_city' } Suburb: + %input.chunky{ id: 'enterprise_city', name: 'city', required: true, placeholder: "e.g. Northcote", ng: { model: 'enterprise.address.city' } } + %span.error{ ng: { show: "details.city.$error.required && submitted" } } + Please enter a suburb + .small-12.medium-4.large-4.columns + .field + %label{ for: 'enterprise_zipcode' } Postcode: + %input.chunky{ id: 'enterprise_zipcode', name: 'zipcode', required: true, placeholder: "e.g. 3070", ng: { model: 'enterprise.address.zipcode' } } + %span.error{ ng: { show: "details.zipcode.$error.required && submitted" } } + Postcode required .row - .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } - %a.panel#producer-panel{ href: "#", ng: { click: "enterprise.is_distributor = false; enterprise.is_primary_producer = true", class: "{selected: (!enterprise.is_distributor && enterprise.is_primary_producer)}" } } - .left - / %render-svg{ path: "/assets/map-icon-producer.svg" } - %h4 I'm A Producer - %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. - .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } - %a.panel#hub-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = false", class: "{selected: (enterprise.is_distributor && !enterprise.is_primary_producer)}" } } - .left - / %render-svg{ path: "/assets/map-icon-hub.svg" } - %h4 I'm A Hub - %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc. - .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } - %a.panel#both-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = true", class: "{selected: (enterprise.is_distributor && enterprise.is_primary_producer)}" } } - .left - / %render-svg{path: "/assets/map-icon-both.svg"} - %h4 I'm Both - %p Hey there, Jack-of-all-trades! Not only do you produce things to eat &/or drink, you also want to sell your yummies through an Open Food Network shopfront. + .small-12.medium-4.large-4.columns + .field + %label{ for: 'enterprise_state' } State: + %select.chunky{ id: 'enterprise_state', name: 'state', ng: { model: 'enterprise.address.state_id', options: 's.id as s.abbr for s in enterprise.country.states', show: 'countryHasStates()', required: 'countryHasStates()' } } + %span.error{ ng: { show: "details.state.$error.required && submitted" } } + State required + .small-12.medium-8.large-8.columns + .field + %label{ for: 'enterprise_country' } Country: + %select.chunky{ id: 'enterprise_country', name: 'country', required: true, ng: { model: 'enterprise.country', options: 'c as c.name for c in countries' } } + %span.error{ ng: { show: "details.country.$error.required && submitted" } } + Please select a country + / .small-12.medium-12.large-5.hide-for-small-only + / %h6 + / Location display + / %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your enterprise's address on the Open Food Network. By default, full location is shown everywhere including street name and number."} + / .row + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'enterpise_suburb_only', ng: { model: 'enterprise.suburb_only' } } + / Hide my street name and street number from the public (ie. only show the suburb) + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'enterprise_on_map', ng: { model: 'enterprise.on_map' } } + / Blur my location on the map (show an approximate, not exact pin) + + .row.buttons .small-12.columns + %hr %input.button.primary.right{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml index 75489c607a..f647a2d8bb 100644 --- a/app/assets/javascripts/templates/registration/finished.html.haml +++ b/app/assets/javascripts/templates/registration/finished.html.haml @@ -1,18 +1,24 @@ .container#registration-finished - .header - %h2 Well done! - %h5 - You have successfully completed the profile for - %span.brick{"ng-show" => "enterprise.is_distributor"} - {{ enterprise.name }} - %span.turquoise{"ng-show" => "!enterprise.is_distributor" } - {{ enterprise.name }} - .content{ style: 'text-align: center'} - %h3 Why not check it out on the Open Food Network? - %a.button.primary{ type: "button", href: "/map" } Go to Map Page > + .row + .small-12.columns.pad-top + %header + %h2 Finished! + .panel.callout + %p + Thanks for filling out the details for + %span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } } + {{ enterprise.name }} + %p You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin. + .row + .small-12.columns.text-center + %h4 + Activate + %span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } } + {{ enterprise.name }} - %br - %br + %p + We've sent a confirmation email to + %strong {{ enterprise.email }}. + %br Please follow the instructions there to make your enterprise visible on the Open Food Network. - %h3 Next step - add some products: - %a.button.primary{ type: "button", href: "/admin/products/new" } Add a Product > + %a.button.primary{ type: "button", href: "/" } Open Food Network home > diff --git a/app/assets/javascripts/templates/registration/images.html.haml b/app/assets/javascripts/templates/registration/images.html.haml index efd3688076..5b2ac39b5a 100644 --- a/app/assets/javascripts/templates/registration/images.html.haml +++ b/app/assets/javascripts/templates/registration/images.html.haml @@ -1,14 +1,22 @@ -.container#registration-images - .header - %h2 Thanks! - %h5 Let's upload some pretty pictures so your profile looks great! :) +.container#registration-images{ 'nv-file-drop' => true, uploader: "imageUploader", options:"{ alias: imageStep }", ng: { controller: "EnterpriseImageCtrl" } } %ng-include{ src: "'registration/steps.html'" } - .row.content - - .row.buttons + .row .small-12.columns - %input.button.primary.left{ type: "button", value: "Back", ng: { click: "select('about')" } } -   - %input.button.primary.right{ type: "button", value: "Continue", ng: { click: "select('social')" } } - - \ No newline at end of file + %header + %h2 Thanks! + %h5 Let's upload some pretty pictures so your profile looks great! :) + + %form{ name: 'images', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "select('social')" } } + .row{ ng: { repeat: 'image_step in imageSteps', show: "imageStep == image_step" } } + %ng-include{ src: "'registration/images/'+ image_step + '.html'" } + + .row.buttons.pad-top{ ng: { if: "imageStep == 'logo'" } } + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('about')" } } +   + %input.button.primary.right{ type: "button", value: "Continue", ng: { click: "imageSelect('promo')" } } + + .row.buttons.pad-top{ ng: { if: "imageStep == 'promo'" } } + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "imageSelect('logo')" } } + %input.button.primary.right{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/images/logo.html.haml b/app/assets/javascripts/templates/registration/images/logo.html.haml new file mode 100644 index 0000000000..4b19c14cff --- /dev/null +++ b/app/assets/javascripts/templates/registration/images/logo.html.haml @@ -0,0 +1,45 @@ +.small-12.medium-12.large-6.columns + .row + .small-12.columns.center + .row + .small-12.columns.center + %h4 + Step 1. Select Logo Image + .row + .small-12.columns.center + %span.small + Tip: Square images will work best, preferably at least 300×300px + .row.pad-top + .small-12.columns + .image-select.small-12.columns + %label.small-12.columns.button{ for: 'image-select' } Choose a logo image + %input#image-select{ type: 'file', hidden: true, 'nv-file-select' => true, uploader: "imageUploader", options: '{ alias: imageStep }' } + .row.show-for-large-up + .large-12.columns + %span#or.large-12.columns + OR + .row.show-for-large-up + .large-12.columns + #image-over{ 'nv-file-over' => true, uploader: "imageUploader" } + Drag and drop your logo here +.small-12.medium-12.large-6.columns + .row + .small-12.columns.center + .row + .small-12.columns.center + %h4 + Step 2. Review Your Logo + .row + .small-12.columns.center + %span.small + Tip: for best results, your logo should fill the available space + .row.pad-top + .small-12.columns.center + #image-placeholder.logo + %img{ ng: { show: "imageSrc() && !imageUploader.isUploading", src: '{{ imageSrc() }}' } } + .message{ ng: { hide: "imageSrc() || imageUploader.isUploading" } } + Your logo will appear here for review once uploaded + .loading{ ng: { hide: "!imageUploader.isUploading" } } + %img.spinner{ src: "/assets/loading.gif" } + %br/ + Uploading... diff --git a/app/assets/javascripts/templates/registration/images/promo.html.haml b/app/assets/javascripts/templates/registration/images/promo.html.haml new file mode 100644 index 0000000000..d98721dc6d --- /dev/null +++ b/app/assets/javascripts/templates/registration/images/promo.html.haml @@ -0,0 +1,43 @@ +.small-12.medium-12.large-12.columns + .row + .small-12.columns.center + %h4 + Step 3. Select Promo Image + .row + .small-12.medium-12.large-5.columns.center + .row + .small-12.columns.center + %span.small + Tip: Shown as a banner, preferred size is 1200×260px + .row.pad-top + .small-12.columns + .image-select.small-12.columns + %label.small-12.columns.button{ for: 'image-select' } Choose a promo image + %input#image-select{ type: 'file', hidden: true, 'nv-file-select' => true, uploader: "imageUploader", options: '{ alias: imageStep }' } + .large-2.columns + %span#or.horizontal.large-12.columns + OR + .large-5.columns + #image-over{ 'nv-file-over' => true, uploader: "imageUploader" } + Drag and drop your promo here +.small-12.medium-12.large-12.columns.pad-top + .row + .small-12.columns.center + %h4 + Step 4. Review Your Promo Banner + .row + .small-12.columns.center + .row + .small-12.columns.center + %span.small + Tip: for best results, your promo image should fill the available space + .row.pad-top + .small-12.columns.center + #image-placeholder.promo + %img{ ng: { show: "imageSrc() && !imageUploader.isUploading", src: '{{ imageSrc() }}' } } + .message{ ng: { hide: "imageSrc() || imageUploader.isUploading" } } + Your logo will appear here for review once uploaded + .loading{ ng: { hide: "!imageUploader.isUploading" } } + %img.spinner{ src: "/assets/loading.gif" } + %br/ + Uploading... diff --git a/app/assets/javascripts/templates/registration/introduction.html.haml b/app/assets/javascripts/templates/registration/introduction.html.haml index 8a4f4f7e02..60a8547b4a 100644 --- a/app/assets/javascripts/templates/registration/introduction.html.haml +++ b/app/assets/javascripts/templates/registration/introduction.html.haml @@ -1,40 +1,45 @@ -%div - .header - %h2 Hi there! - %h4 This wizard will step you through creating a profile - .row - .small-12.medium-3.large-2.columns.text-right.hide-for-small-only - %img{:src => "/assets/potatoes.png"} - .small-12.medium-9.large-10.columns - %p - Your profile gives you an online presence on the - %strong Open Food Network, - allowing you to easily connect with potential customers or partners. You can always choose to update your info later, as well as choose to upgrade your Profile to and Online Store, where you can sell products, track orders and receive payments. Creating a profile takes about 5-10 minutes. - .row{ 'data-equalizer' => true } - .small-12.medium-6.large-6.columns.pad-top{ 'data-equalizer-watch' => true } - %h5 You'll need the following: - %ul.check-list - %li - Your enterprise address and contact details - %li - Your logo image - %li - A pretty picture for your profile header - %li - Some 'About Us' text - - .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true} - .highlight-box - %h5 Your profile entitles you to: - %ul.small-block-grid-1 - %li - %i.ofn-i_020-search - A searchable listing - %li - %i.ofn-i_040-hub - A pin on the OFN map - .row - .small-12.columns +.row + .small-12.columns + %header + %h2 Hi there! + %h4 + %small + %i.ofn-i_040-hub + Create your enterprise profile + .hide-for-large-up %hr - %input.button.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } } - \ No newline at end of file + %input.button.small.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } } + %hr + +.row{ 'data-equalizer' => true } + .small-12.medium-12.large-6.columns.pad-top{ 'data-equalizer-watch' => true } + %h5 You'll need: + %ul.check-list + %li + 5-10 minutes + %li + Enterprise address + %li + Primary contact details + %li + Your logo image + %li + Landscape image for your profile + %li + 'About Us' text + + .small-9.medium-8.large-5.columns.pad-top.end{ 'data-equalizer-watch' => true} + %h5 + What do I get? + %p + Your profile helps people + %strong find + and + %strong contact + you on the Open Food Network. + %p Use this space to tell the story of your enterprise, to help drive connections to your social and online presence. + +.row.show-for-large-up + .small-12.columns + %hr + %input.button.primary.right{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } } diff --git a/app/assets/javascripts/templates/registration/limit_reached.html.haml b/app/assets/javascripts/templates/registration/limit_reached.html.haml new file mode 100644 index 0000000000..778d980289 --- /dev/null +++ b/app/assets/javascripts/templates/registration/limit_reached.html.haml @@ -0,0 +1,16 @@ +.row + .small-12.columns + %header + %h2 Oh no! + %h4 You have reached the limit! +.row + .small-12.medium-3.large-2.columns.text-right.hide-for-small-only + %img{:src => "/assets/potatoes.png"} + .small-12.medium-9.large-10.columns + %p + You have reached the limit for the number of enterprises you are allowed to own on the + %strong Open Food Network. +.row + .small-12.columns + %hr + %input.button.primary{ type: "button", value: "Return to the homepage", ng: { click: "close()" } } diff --git a/app/assets/javascripts/templates/registration/social.html.haml b/app/assets/javascripts/templates/registration/social.html.haml index 2aa12bd08b..4f448734f1 100644 --- a/app/assets/javascripts/templates/registration/social.html.haml +++ b/app/assets/javascripts/templates/registration/social.html.haml @@ -1,35 +1,49 @@ .container#registration-social - .header - %h2 Last step! - %h5 How can people find {{ enterprise.name }} online? %ng-include{ src: "'registration/steps.html'" } + + .row + .small-12.columns + %header + %h2 Final step! + %h5 + How can people find + %span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } } + {{ enterprise.name }} + online? + %form{ name: 'social', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('finished',social)" } } .row.content .small-12.large-7.columns .row .small-12.columns - %label{ for: 'enterprise_website' } Website: - %input.chunky.small-12.columns{ id: 'enterprise_website', placeholder: "eg. openfoodnetwork.org.au", ng: { model: 'enterprise.website' } } + .field + %label{ for: 'enterprise_website' } Website: + %input.chunky{ id: 'enterprise_website', placeholder: "eg. openfoodnetwork.org.au", ng: { model: 'enterprise.website' } } .row .small-12.columns - %label{ for: 'enterprise_facebook' } Facebook: - %input.chunky.small-12.columns{ id: 'enterprise_facebook', placeholder: "eg. www.facebook.com/PageNameHere", ng: { model: 'enterprise.facebook' } } + .field + %label{ for: 'enterprise_facebook' } Facebook: + %input.chunky{ id: 'enterprise_facebook', placeholder: "eg. www.facebook.com/PageNameHere", ng: { model: 'enterprise.facebook' } } .row .small-12.columns - %label{ for: 'enterprise_linkedin' } LinkedIn: - %input.chunky.small-12.columns{ id: 'enterprise_linkedin', placeholder: "eg. www.linkedin.com/YourNameHere", ng: { model: 'enterprise.linkedin' } } + .field + %label{ for: 'enterprise_linkedin' } LinkedIn: + %input.chunky{ id: 'enterprise_linkedin', placeholder: "eg. www.linkedin.com/YourNameHere", ng: { model: 'enterprise.linkedin' } } .small-12.large-5.columns .row .small-12.columns - %label{ for: 'enterprise_twitter' } Twitter: - %input.chunky.small-12.columns{ id: 'enterprise_twitter', placeholder: "eg. @twitter_handle", ng: { model: 'enterprise.twitter' } } + .field + %label{ for: 'enterprise_twitter' } Twitter: + %input.chunky{ id: 'enterprise_twitter', placeholder: "eg. @twitter_handle", ng: { model: 'enterprise.twitter' } } .row .small-12.columns - %label{ for: 'enterprise_instagram' } Instagram: - %input.chunky.small-12.columns{ id: 'enterprise_instagram', placeholder: "eg. @instagram_handle", ng: { model: 'enterprise.instagram' } } + .field + %label{ for: 'enterprise_instagram' } Instagram: + %input.chunky{ id: 'enterprise_instagram', placeholder: "eg. @instagram_handle", ng: { model: 'enterprise.instagram' } } .row.buttons .small-12.columns - %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('about')" } } -   - %input.button.primary{ type: "submit", value: "Continue" } \ No newline at end of file + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('images')" } } + %input.button.primary.right{ type: "submit", value: "Continue" } + + diff --git a/app/assets/javascripts/templates/registration/steps.html.haml b/app/assets/javascripts/templates/registration/steps.html.haml index 65e3cb6b48..8b420de293 100644 --- a/app/assets/javascripts/templates/registration/steps.html.haml +++ b/app/assets/javascripts/templates/registration/steps.html.haml @@ -1,5 +1,3 @@ .row#progress-bar .small-12.medium-2.columns.item{ ng: { repeat: 'step in steps', class: "{active: (currentStep() == step),'show-for-medium-up': (currentStep() != step)}" } } {{ $index+1 + ". " + step }} - - \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration/type.html.haml b/app/assets/javascripts/templates/registration/type.html.haml new file mode 100644 index 0000000000..48d45cb66a --- /dev/null +++ b/app/assets/javascripts/templates/registration/type.html.haml @@ -0,0 +1,46 @@ +.container#registration-type{bindonce: true} + + %ng-include{ src: "'registration/steps.html'" } + + .row + .small-12.columns + %header + %h2 + Last step to add + %span{ ng: { class: "{brick: !enterprise.is_primary_producer, turquoise: enterprise.is_primary_producer}" } } + {{ enterprise.name }}! + %h4 + Are you a producer? + + %form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } } + .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } } + .small-12.columns.field + .row + .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } + %a.btnpanel#producer-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = true", class: "{selected: enterprise.is_primary_producer}" } } + %i.ofn-i_059-producer + %h4 Yes, I'm a producer + + .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } + %a.btnpanel#hub-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = false", class: "{selected: enterprise.is_primary_producer == false}" } } + %i.ofn-i_063-hub + %h4 No, I'm not a producer + + .row + .small-12.columns + %input.chunky{ id: 'enterprise_is_primary_producer', name: 'is_primary_producer', hidden: true, required: true, ng: { model: 'enterprise.is_primary_producer' } } + %span.error{ ng: { show: "type.is_primary_producer.$error.required && submitted" } } + Please choose one. Are you are producer? + .row + .small-12.columns + .panel.callout + .left + %i.ofn-i_013-help +   + %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. + / %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc. + + .row.buttons + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('contact')" } } + %input.button.primary.right{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index a6db8b4e41..b1538bb69e 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -48,7 +48,7 @@ .small-4.medium-2.large-2.columns.variant-price .table-cell.price %i.ofn-i_009-close - {{ variant.price | currency }} + {{ variant.price_with_fees | localizeCurrency }} -# Now in a template in app/assets/javascripts/templates ! %price-breakdown{"price-breakdown" => "_", variant: "variant", @@ -59,4 +59,4 @@ .small-12.medium-2.large-2.columns.total-price.text-right .table-cell %strong - {{ variant.getPrice() | currency }} + {{ variant.totalPrice() | localizeCurrency }} diff --git a/app/assets/stylesheets/admin/alert.css.sass b/app/assets/stylesheets/admin/alert.css.sass new file mode 100644 index 0000000000..d86cb38f55 --- /dev/null +++ b/app/assets/stylesheets/admin/alert.css.sass @@ -0,0 +1,15 @@ +.alert + border: 3px solid #919191 + border-radius: 6px + margin-bottom: 20px + color: #919191 + padding: 5px 10px + h6 + color: #919191 + .message + font-weight: bold + &:hover + border-color: #DA5354 + color: #DA5354 + h6 + color: #DA5354 diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.css index 7c3345de6b..9b3603fe7b 100644 --- a/app/assets/stylesheets/admin/all.css +++ b/app/assets/stylesheets/admin/all.css @@ -9,6 +9,7 @@ *= require admin/spree_promo *= require shared/jquery-ui-timepicker-addon + *= require shared/textAngular.min *= require_self *= require_tree . diff --git a/app/assets/stylesheets/admin/dashboard_item.css.sass b/app/assets/stylesheets/admin/dashboard_item.css.sass index e24d60b277..13b16084f6 100644 --- a/app/assets/stylesheets/admin/dashboard_item.css.sass +++ b/app/assets/stylesheets/admin/dashboard_item.css.sass @@ -156,4 +156,4 @@ div.dashboard_item background-color: #9fc820 &.bottom border-radius: 0px 0px 6px 6px - padding: 15px 15px \ No newline at end of file + padding: 15px 15px diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 3281c8d116..8bf19ea6b0 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -210,3 +210,24 @@ table#listing_enterprise_groups { color: #575757; } +.field_with_errors > input { + border-color: red; +} + +// textAngular wysiwyg +text-angular { + .ta-scroll-window > .ta-bind { + max-height: 400px; + min-height: 100px; + } + .ta-scroll-window.form-control { + min-height: 100px; + } + .btn-group { + display: inline; + margin-right: 8px; + button { + padding: 5px 10px; + } + } +} diff --git a/app/assets/stylesheets/admin/sidebar-item.css.sass b/app/assets/stylesheets/admin/sidebar-item.css.sass index ee7542f138..d25d43ab29 100644 --- a/app/assets/stylesheets/admin/sidebar-item.css.sass +++ b/app/assets/stylesheets/admin/sidebar-item.css.sass @@ -33,6 +33,9 @@ div.sidebar_item color: #DA5354 .list-item + .icon-arrow-right + padding-top: 6px + font-size: 20px border: solid #5498da border-width: 0px 1px 0px 1px a.alpha, span.alpha @@ -40,7 +43,6 @@ div.sidebar_item margin-left: -1px padding: 10px 2px 10px 5% overflow: hidden - max-width: 160px text-overflow: ellipsis span.omega padding: 8px 18px 8px 0px @@ -72,4 +74,4 @@ div.sidebar_item background-color: #DA5354 &:hover background-color: #9fc820 - \ No newline at end of file + diff --git a/app/assets/stylesheets/darkswarm/branding.css.sass b/app/assets/stylesheets/darkswarm/branding.css.sass index 011ef2ff68..0fa559240f 100644 --- a/app/assets/stylesheets/darkswarm/branding.css.sass +++ b/app/assets/stylesheets/darkswarm/branding.css.sass @@ -17,7 +17,9 @@ $clr-blue-bright: #14b6cc $disabled-light: #e5e5e5 $disabled-bright: #ccc +$disabled-med: #b3b3b3 $disabled-dark: #999 +$disabled-v-dark: #808080 $med-grey: #666 $dark-grey: #333 - +$black: #000 diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass index eb539cc0b5..d02f9b5299 100644 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ b/app/assets/stylesheets/darkswarm/hub_node.css.sass @@ -87,6 +87,8 @@ &.inactive &.closed, &.open &, & * + color: $disabled-med + a, a * color: $disabled-dark &.closed .active_table_row, .active_table_row:first-child, .active_table_row:last-child @@ -126,3 +128,13 @@ .active_table_row:first-child .skinny-head background-color: $disabled-light + //Is Profile - profile node + &.inactive.is_profile + &.closed, &.open + .active_table_row + &:hover, &:active, &:focus + border-color: transparent + cursor: auto + @media all and (max-width: 640px) + border-color: transparent + diff --git a/app/assets/stylesheets/darkswarm/images.css.sass b/app/assets/stylesheets/darkswarm/images.css.sass index ce205f0dae..f94240b4af 100644 --- a/app/assets/stylesheets/darkswarm/images.css.sass +++ b/app/assets/stylesheets/darkswarm/images.css.sass @@ -11,12 +11,16 @@ @include box-shadow(0 1px 2px 1px rgba(0,0,0,0.25)) .hero-img - border-bottom: 1px solid $disabled-bright + outline: 1px solid $disabled-bright + border-color: transparent + @include box-shadow(none) width: 100% - min-height: 56px + min-height: 80px height: inherit max-height: 260px overflow: hidden + @media all and (max-width: 640px) + min-height: 68px .hero-img-small background-color: #333 diff --git a/app/assets/stylesheets/darkswarm/mixins.sass b/app/assets/stylesheets/darkswarm/mixins.sass index 6eef888a17..6f4cb65440 100644 --- a/app/assets/stylesheets/darkswarm/mixins.sass +++ b/app/assets/stylesheets/darkswarm/mixins.sass @@ -16,6 +16,20 @@ -webkit-box-shadow: $box-shadow box-shadow: $box-shadow +@mixin elipse-shadow($elipse-shadow) + content: "" + position: absolute + z-index: -1 + -webkit-box-shadow: $elipse-shadow + box-shadow: $elipse-shadow + bottom: -12% + left: 10% + right: 10% + width: 80% + height: 10% + -moz-border-radius: 100% + border-radius: 100% + @mixin border-radius($border-radius) -webkit-border-radius: $border-radius border-radius: $border-radius diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass index 9f08b38790..305566ad58 100644 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass @@ -25,23 +25,39 @@ position: relative .highlight-top - padding: 0.75rem 0.9375rem - width: 100% - overflow: hidden + padding-top: 0.75rem + padding-bottom: 0.75rem background-color: rgba(255,255,255,0.65) position: absolute bottom: 0 + width: 100% + border: 0 + outline: 0 + @media only screen and (max-width: 640px) + padding-top: 0.5rem + padding-bottom: 0.35rem + h3, p margin-top: 0 margin-bottom: 0 padding-bottom: 0 line-height: 1 + + h3 > i + color: $clr-brick + p - line-height: 2 + line-height: 2.4 + @media all and (max-width: 640px) + line-height: 1.4 h3 a:hover span border-bottom: 1px solid $clr-brick-bright + .is_producer + &, & * + color: $clr-turquoise + // ABOUT Enterprise diff --git a/app/assets/stylesheets/darkswarm/modal-login.css.sass b/app/assets/stylesheets/darkswarm/modal-login.css.sass index 38fe5dee3a..61108145bb 100644 --- a/app/assets/stylesheets/darkswarm/modal-login.css.sass +++ b/app/assets/stylesheets/darkswarm/modal-login.css.sass @@ -1,6 +1,13 @@ // Styling for login modal to style tabs +.reveal-modal.login-modal + border-bottom-color: #efefef + .login-modal background: #efefef .tabs-content - background: #fff \ No newline at end of file + background: #fff + padding-top: 10px + + + \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/modals.css.sass b/app/assets/stylesheets/darkswarm/modals.css.sass index e6303a4cd7..578a3ceccb 100644 --- a/app/assets/stylesheets/darkswarm/modals.css.sass +++ b/app/assets/stylesheets/darkswarm/modals.css.sass @@ -4,8 +4,14 @@ dialog, .reveal-modal border: none outline: none - padding: 1rem + padding: 30px 20px 0 20px + border-bottom: 30px solid white overflow-y: scroll + overflow-x: hidden + // Not working yet - want a nice gradient shadow when there is overflow - needs JS too + // &:after + // @include elipse-shadow(0 0 40px rgba(0, 0, 0, 0.8)) + // Reveal.js break point: // @media only screen and (max-width: 40.063em) @@ -25,14 +31,18 @@ dialog, .reveal-modal max-height: 80% .reveal-modal-bg - background-color: rgba(0,0,0,0.65) + background-color: rgba(0,0,0,0.85) dialog .close-reveal-modal, .reveal-modal .close-reveal-modal - right: 0.4rem - background-color: rgba(235,235,235,0.85) + right: 0.25rem + top: 0.25rem + background-color: rgba(205,205,205,0.65) text-shadow: none - padding: 0.3rem + font-size: 2rem + padding: 0.45rem + color: #666 + z-index: 9999999 @include border-radius(999999rem) &:hover, &:active, &:focus - background-color: rgba(235,235,235,1) + background-color: rgba(205,205,205,1) color: #333 diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.sass b/app/assets/stylesheets/darkswarm/producer_node.css.sass index 08c9fd87ef..7bac2dde6d 100644 --- a/app/assets/stylesheets/darkswarm/producer_node.css.sass +++ b/app/assets/stylesheets/darkswarm/producer_node.css.sass @@ -32,6 +32,11 @@ span text-decoration: underline + &.is_distributor, &.is_distributor i.ofn-i_059-producer, &.is_distributor i.ofn-i_060-producer-reversed + color: $clr-brick + &:hover, &:active, &:focus + color: $clr-brick-bright + a.cta-hub &:hover, &:focus, &:active &.secondary @@ -51,9 +56,11 @@ .fat-taxons background-color: $clr-turquoise-light + .producer-name + color: $clr-turquoise + //Open row &.open - .active_table_row border-left: 1px solid $clr-turquoise-bright border-right: 1px solid $clr-turquoise-bright diff --git a/app/assets/stylesheets/darkswarm/registration.css.sass b/app/assets/stylesheets/darkswarm/registration.css.sass index 3e851709bc..f63eb30421 100644 --- a/app/assets/stylesheets/darkswarm/registration.css.sass +++ b/app/assets/stylesheets/darkswarm/registration.css.sass @@ -2,44 +2,25 @@ @import mixins #registration-modal - .header + header text-align: center - background-color: #efefef - padding-bottom: 1rem + // background-color: #efefef + @media all and (max-width: 64em) + text-align: left .container background-color: #ffffff - .content - // margin-bottom: 15px i font-size: 150% - .buttons - - ofn-inline-flash - display: block - padding: 15px - position: relative - margin-bottom: 10px - &.brick - background-color: $clr-brick-light - border: 2px solid $clr-brick - color: $clr-brick - &.turquoise - background-color: $clr-turquoise-light - border: 2px solid $clr-turquoise - color: $clr-turquoise - .close-button - position: absolute - top: 0px - right: 0px - .field - margin-bottom: 15px + margin-bottom: 1em - input.chunky + .chunky padding: 8px - font-size: 105% + font-size: 1rem + margin: 0 + width: 100% label.indent-checkbox display: block @@ -51,9 +32,9 @@ label margin-bottom: 3px - ol, ul - // font-size: 80% + ol, ul, p font-size: 0.875rem + ol, ul padding: 0 margin: 0 ol @@ -62,40 +43,107 @@ .highlight-box background: white padding: 1rem 1.2rem - @media all and (max-width: 640px) + @media all and (max-width: 64em) margin-top: 1rem #progress-bar margin-bottom: 15px .item - padding: 12px 0px + font-size: 0.75rem + padding: 10px 0px text-transform: uppercase text-align: center - background-color: #333 - border: 2px solid #333 + background-color: $clr-blue + border: 2px solid $clr-blue color: #fff .item.active - background-color: #cccccc - border: 2px solid #333 - color: #333 + background-color: $disabled-light + border: 2px solid $clr-blue + color: $clr-blue + font-weight: 700 @include box-shadow(inset 0 0 1px 0 #fff) -#registration-details + + .image-select + label + font-size: 18px + padding: 21px 0px + #logo-select + display: none + + #image-over + font-size: 18px + padding: 41px 0px + border: 3px dashed #494949 + text-align: center + font-weight: bold + color: #494949 + &.nv-file-over + background-color: #78cd91 + + #or + text-align: center + font-weight: bold + font-size: 18px + padding: 21px 0px + &.horizontal + padding: 41px 0px + + #image-placeholder + font-size: 18px + font-weight: bold + color: #373737 + background-color: #f1f1f1 + text-align: center + border: 3px dashed #494949 + margin-left: auto + margin-right: auto + .spinner + width: 100px + &.logo + .message + padding-top: 6em + .loading + padding-top: 4em + width: 306px + height: 306px + &.promo + .message + padding-top: 4em + .loading + padding-top: 1em + width: 726px + height: 166px + + +#registration-type #enterprise-types - a.panel + a.btnpanel display: block + padding: 1rem + margin-bottom: 1rem background-color: #efefef color: black - @media all and (min-width: 768px) - min-height: 200px + text-align: center + border: 1px solid transparent + i + font-size: 3rem + h4 + margin-top: 1rem + &:hover background-color: #fff + &#producer-panel:hover + border: 1px solid $clr-turquoise &, & * color: $clr-turquoise + &#hub-panel:hover, &#both-panel:hover + border: 1px solid $clr-brick &, & * color: $clr-brick + &.selected &, & * color: #fff @@ -112,4 +160,3 @@ p clear: both font-size: 0.875rem - diff --git a/app/assets/stylesheets/darkswarm/ui.css.sass b/app/assets/stylesheets/darkswarm/ui.css.sass index 3bbce61552..d18adae1b4 100644 --- a/app/assets/stylesheets/darkswarm/ui.css.sass +++ b/app/assets/stylesheets/darkswarm/ui.css.sass @@ -74,8 +74,10 @@ button.success, .button.success .profile-checkbox display: inline-block - input[type="checkbox"] + label + label margin: 0 0.2rem + float: right + // Responsive @media screen and (min-width: 768px) diff --git a/app/assets/stylesheets/distributors.css.scss b/app/assets/stylesheets/distributors.css.scss deleted file mode 100644 index de8cd669a0..0000000000 --- a/app/assets/stylesheets/distributors.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the distributors controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/groups.css.scss b/app/assets/stylesheets/groups.css.scss deleted file mode 100644 index c2a5f9013b..0000000000 --- a/app/assets/stylesheets/groups.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the groups controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/shared/textAngular.min.css b/app/assets/stylesheets/shared/textAngular.min.css new file mode 100644 index 0000000000..6f132953bb --- /dev/null +++ b/app/assets/stylesheets/shared/textAngular.min.css @@ -0,0 +1 @@ +.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/app/assets/stylesheets/shop/checkout.css.scss b/app/assets/stylesheets/shop/checkout.css.scss deleted file mode 100644 index 727d410b19..0000000000 --- a/app/assets/stylesheets/shop/checkout.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the Shop::Checkout controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/store/openfoodnetwork.css.scss b/app/assets/stylesheets/store/openfoodnetwork.css.scss deleted file mode 100644 index f98642cc22..0000000000 --- a/app/assets/stylesheets/store/openfoodnetwork.css.scss +++ /dev/null @@ -1,381 +0,0 @@ -@import "screen"; -@import "compass/css3/border-radius"; - -/* General purpose styles */ - -a:hover { - color: lighten($link_text_color, 20) !important; -} - -/* Cleared div for clearing previous floating elements */ -div.cleared { - clear: both; -} - -p.hint { - margin: 0 0 0.5em 0; - padding: 0; - color: #6a6a6a; -} - -table { - tbody, tfoot { - tr { - &.alt, &.odd { - background-color: lighten($link_text_color, 75) !important; - } - } - } -} - - - -/* Style current distributor in main nav bar */ -#header { - margin-bottom: 40px; - - #logo { - position: relative; - margin-top: 40px; - padding-top: 10px; - - h1 { - position: absolute; - top: 10px; - left: 0px; - } - - .change-location { - position: absolute; - top: 22px; - right: -104px; - } - } -} - -nav #main-nav-bar { - position: absolute; - top: 0px; - left: 10px; - width: 100%; - - #link-to-cart { - position: relative; - top: 40px; - right: 16px; - } - - li { - &#current-distribution { - float: right; - clear: right; - margin: 0.5em 5px 0 0; - - a { - font-size: 12px; - padding: 0; - } - } - } -} - -nav#top-nav-bar { - position: relative; - z-index: 999; - margin-top: 20px; -} - - -/* Based on Spree's nav#taxonomies style. Copied instead of - * extended with SASS because SASS does not allow extending - * nested selectors. - */ -nav#filters { - .filter_name { - text-transform: uppercase; - border-bottom: 1px solid lighten($body_text_color, 60); - margin-bottom: 5px; - font-size: $main_navigation_header_font_size; - } - - .filter_choices { - padding-left: 20px; - margin-bottom: 5px; - list-style: disc outside; - - li { - a { - font-size: $main_navigation_font_size; - } - span.inactive { - color: #999; - } - } - } - - input[type=submit] { - margin-bottom: 15px; - } -} - - -/* Distributor and order cycle selection and display */ -#distribution-choice { - margin-bottom: 2em; - padding: 5px; - border: 2px solid #ccc; - @include border-radius(10px); -} - -#distribution-selection { - overflow: auto; - margin-bottom: 2em; - padding: 20px; - background-color: #f3f3f3; - - .distributors { - float: left; - margin-right: 4em; - - option.local { - background-color: #cfc; - } - option.remote { - background-color: #fcc; - } - } - - .order-cycles { - - select { - padding: 10px; - margin-top: 15px; - border: 2px solid white; - width: 100%; - } - - float: left; - - tr.local { - background-color: #cfc; - } - tr.remote { - background-color: #fcc; - } - } -} - -.countdown-panel { - background: url("../countdown.png") no-repeat left; - min-height: 60px; - min-width: 70px; - padding-left: 77px; -} - - -/* Style the product source on the product details page in the - * same manner as the product properties table above it. - */ -#product-source { - @extend #product-properties; -} -#product-properties td, #product-source td { - width: 50%; -} - - -/* Apply Spree's ul#products style to ul.product-listing. When viewing products - * split by distributor, a separate product listing is displayed for local and - * remote products, so the #products id is removed to avoid its duplication. - */ -ul.product-listing { - &:after { - content: " "; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; - } - - li { - text-align: center; - font-weight: bold; - margin-bottom: 20px; - - a { - display: block; - - &.info { - height: 35px; - margin-top: 5px; - font-size: $product_list_name_font_size; - color: $product_link_text_color; - border-bottom: 1px solid lighten($body_text_color, 60); - overflow: hidden; - } - } - - .product-image { - border: 1px solid lighten($body_text_color, 60); - padding: 5px; - min-height: 110px; - background-color: $product_background_color; - - &:hover { - border-color: $link_text_color; - } - - } - - .price { - color: $link_text_color; - font-size: $product_list_price_font_size; - padding-top: 5px; - display: block; - } - } -} - - -/* Enterprise description */ -.enterprise-description { - margin-bottom: 2em; -} - - -/* Supplier page distributor listing */ -#supplier-distributors li a.inactive { - color: #999; -} - - -/* Highlight local products in distributor-split product listings */ -#products-local ul { - margin-bottom: 1em; - padding: 10px; - @include border-radius(10px); - background-color: #def; -} - - -/* Distributor details on product details page */ -fieldset#product-distributor-details { - float: right; - margin-top: 0; - width: 250px; - @extend #shipping; -} - -#product-variants { - float: none; - - ul { - list-style-type: none; - } -} - - -/* Add to cart form on product details page */ -#cart-form { - .error-distributor { - font-size: 120%; - font-weight: bold; - color: #f00; - - a { - color: #f00; - text-decoration: underline; - } - } - - .distributor-fixed { - } -} - - -/* View cart form */ -#subtotal { - width: 100%; -} -.links { - float: right; - text-align: right; -} -#empty-cart { - float: left; - margin-top: 15px !important; - - p { - padding: 0; - } -} - -/* Checkout address page */ -#checkout .alternative-available-distributors { - padding-top: 30px; -} - - -/* Delivery fees table on checkout page */ -#delivery-fees { - clear: both; - padding-top: 6em; - - table#delivery { - width: 100%; - - tbody { - tr { - td.item-shipping-cost, td.item-shipping-method { - text-align: center; - } - td.item-shipping-cost { - @extend span.price; - @extend span.price.selling; - } - } - } - } - - .subtotal { - width: 100%; - text-align: right; - text-transform: uppercase; - margin-top: 15px; - - span.order-total { - @extend span.price; - } - } -} - -/* Alert for EFT Payment during checkout process */ -div#eft-payment-alert { - border: 2px solid red; - padding-left: 5px; -} - -/* Large 'Save and Continue/Process My Order' button under Order Summary on the checkout pages */ -#add_new_save_checkout_button { - display: none; - margin-top: 30px; - padding-top: 10px; - border-top: 1px solid #d9d9db; -} - -.secondary { - color: #6A6A6A; -} - -.hide { - display: none; -} - - -/* Distributor details */ -.distributor-details .next-collection-at { - font-size: 20px; - font-weight: bold; - color: #de790c; -} diff --git a/app/assets/stylesheets/store/variables.css.scss b/app/assets/stylesheets/store/variables.css.scss deleted file mode 100644 index 4cb074ae48..0000000000 --- a/app/assets/stylesheets/store/variables.css.scss +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------*/ -/* Colors -/*--------------------------------------*/ -$c_green: #8dba53 !default; /* Spree green */ -$c_red: #e45353 !default; /* Error red */ - -$layout_background_color: #FFFFFF !default; -$title_text_color: #404042 !default; -$body_text_color: #404042 !default; -$link_text_color: #00ADEE !default; - -$product_background_color: #FFFFFF !default; -$product_title_text_color: #404042 !default; -$product_body_text_color: #404042 !default; -$product_link_text_color: #BBBBBB !default; - -/*--------------------------------------*/ -/* Fonts import from remote -/*--------------------------------------*/ -@import url(//fonts.googleapis.com/css?family=Ubuntu:400,700,400italic,700italic|&subset=latin,cyrillic,greek,greek-ext,latin-ext,cyrillic-ext); - -/*--------------------------------------*/ -/* Font families -/*--------------------------------------*/ -$ff_base: 'Ubuntu', sans-serif !default; - -/*-------------------------------------- - | Font sizes - |-------------------------------------- - |- Navigation - | */ - $header_navigation_font_size: 14px !default; - $horizontal_navigation_font_size: 16px !default; - $main_navigation_header_font_size: 14px !default; - $main_navigation_font_size: 12px !default; -/*|------------------------------------ - |- Product Listing - | */ - $product_list_name_font_size: 12px !default; - $product_list_price_font_size: 16px !default; - $product_list_header_font_size: 20px !default; - $product_list_search_font_size: 14px !default; -/*|------------------------------------ - |- Product Details - | */ - $product_detail_name_font_size: 24px !default; - $product_detail_description_font_size: 12px !default; - $product_detail_price_font_size: 20px !default; - $product_detail_title_font_size: 14px !default; -/*|------------------------------------ - |- Basic - | */ - $heading_font_size: 24px !default; - $sub_heading_font_size: 14px !default; - $button_font_size: 12px !default; - $input_box_font_size: 13px !default; - $base_font_size: 12px !default; - $border_color: lighten($body_text_color, 60) !default; - $default_border: 1px solid $border_color !default; - $button_border_color: rgba(0, 138, 189, .75) !default; - $table_head_color: lighten($body_text_color, 60) !default; - - -@import "./store/variables_changes.css.scss"; diff --git a/app/assets/stylesheets/store/variables_changes.css.scss b/app/assets/stylesheets/store/variables_changes.css.scss deleted file mode 100644 index c4007abd2f..0000000000 --- a/app/assets/stylesheets/store/variables_changes.css.scss +++ /dev/null @@ -1 +0,0 @@ -$link_text_color: #006066; diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 64ba997950..7d38e408d6 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -3,10 +3,11 @@ module Admin before_filter :load_enterprise_set, :only => :index before_filter :load_countries, :except => :index before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create] - before_filter :check_type, only: :update - before_filter :check_bulk_type, only: :bulk_update + before_filter :check_can_change_sells, only: :update + before_filter :check_can_change_bulk_sells, only: :bulk_update before_filter :override_owner, only: :create - before_filter :check_owner, only: :update + before_filter :check_can_change_owner, only: :update + before_filter :check_can_change_bulk_owner, only: :bulk_update helper 'spree/products' include OrderCyclesHelper @@ -49,7 +50,8 @@ module Admin end def collection - Enterprise.managed_by(spree_current_user).order('is_distributor DESC, is_primary_producer ASC, name') + # TODO was ordered with is_distributor DESC as well, not sure why or how we want ot sort this now + Enterprise.managed_by(spree_current_user).order('is_primary_producer ASC, name') end def collection_actions @@ -62,28 +64,36 @@ module Admin @enterprise_fees = EnterpriseFee.managed_by(spree_current_user).for_enterprise(@enterprise).order(:fee_type, :name).all end - def check_bulk_type + def check_can_change_bulk_sells unless spree_current_user.admin? params[:enterprise_set][:collection_attributes].each do |i, enterprise_params| - enterprise_params.delete :type + enterprise_params.delete :sells end end end - def check_type - params[:enterprise].delete :type unless spree_current_user.admin? + def check_can_change_sells + params[:enterprise].delete :sells unless spree_current_user.admin? end def override_owner params[:enterprise][:owner_id] = spree_current_user.id unless spree_current_user.admin? end - def check_owner - unless spree_current_user == @enterprise.owner || spree_current_user.admin? + def check_can_change_owner + unless ( spree_current_user == @enterprise.owner ) || spree_current_user.admin? params[:enterprise].delete :owner_id end end + def check_can_change_bulk_owner + unless spree_current_user.admin? + params[:enterprise_set][:collection_attributes].each do |i, enterprise_params| + enterprise_params.delete :owner_id + end + end + end + # Overriding method on Spree's resource controller def location_after_save if params[:enterprise].key? :producer_properties_attributes diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index 9a3b715093..76320b0750 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -27,9 +27,9 @@ module Api end def update - authorize! :update, Enterprise - @enterprise = Enterprise.find(params[:id]) + authorize! :update, @enterprise + if @enterprise.update_attributes(params[:enterprise]) render text: @enterprise.id, :status => 200 else @@ -37,6 +37,19 @@ module Api end end + def update_image + @enterprise = Enterprise.find(params[:id]) + authorize! :update, @enterprise + + if params[:logo] && @enterprise.update_attributes( { logo: params[:logo] } ) + render text: @enterprise.logo.url(:medium), :status => 200 + elsif params[:promo] && @enterprise.update_attributes( { promo_image: params[:promo] } ) + render text: @enterprise.promo_image.url(:medium), :status => 200 + else + invalid_resource!(@enterprise) + end + end + private def override_owner diff --git a/app/controllers/devise/confirmations_controller_decorator.rb b/app/controllers/devise/confirmations_controller_decorator.rb new file mode 100644 index 0000000000..ef34f28445 --- /dev/null +++ b/app/controllers/devise/confirmations_controller_decorator.rb @@ -0,0 +1,7 @@ +Devise::ConfirmationsController.class_eval do + protected + # Override of devise method in Devise::ConfirmationsController + def after_confirmation_path_for(resource_name, resource) + spree.admin_path + end +end \ No newline at end of file diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb index d58b10bd0b..5a21d28a44 100644 --- a/app/controllers/registration_controller.rb +++ b/app/controllers/registration_controller.rb @@ -2,17 +2,12 @@ require 'open_food_network/spree_api_key_loader' class RegistrationController < BaseController include OpenFoodNetwork::SpreeApiKeyLoader - before_filter :load_spree_api_key, only: :index + before_filter :load_spree_api_key, only: [:index] before_filter :check_user, except: :authenticate layout 'registration' def index - @enterprise_attributes = { type: 'profile' } - end - - def store - @enterprise_attributes = { is_distributor: true, is_primary_producer: true, type: 'single' } - render :index + @enterprise_attributes = { sells: 'none' } end private @@ -20,6 +15,8 @@ class RegistrationController < BaseController def check_user if spree_current_user.nil? redirect_to registration_auth_path(anchor: "signup?after_login=#{request.env['PATH_INFO']}") + elsif !spree_current_user.can_own_more_enterprises? + render :limit_reached end end end diff --git a/app/controllers/spree/admin/image_settings_controller_decorator.rb b/app/controllers/spree/admin/image_settings_controller_decorator.rb new file mode 100644 index 0000000000..5fe7dae551 --- /dev/null +++ b/app/controllers/spree/admin/image_settings_controller_decorator.rb @@ -0,0 +1,11 @@ +Spree::Admin::ImageSettingsController.class_eval do + # Spree stores attachent definitions in JSON. This converts the style name and format to + # strings. However, when paperclip encounters these, it doesn't recognise the format. + # Here we solve that problem by converting format and style name to symbols. + def update_paperclip_settings_with_format_styles + update_paperclip_settings_without_format_styles + Spree::Image.reformat_styles + end + + alias_method_chain :update_paperclip_settings, :format_styles +end diff --git a/app/controllers/spree/admin/overview_controller_decorator.rb b/app/controllers/spree/admin/overview_controller_decorator.rb index a2288dab88..4ae1c1b095 100644 --- a/app/controllers/spree/admin/overview_controller_decorator.rb +++ b/app/controllers/spree/admin/overview_controller_decorator.rb @@ -1,6 +1,7 @@ Spree::Admin::OverviewController.class_eval do def index - @enterprises = Enterprise.managed_by(spree_current_user).order('is_distributor DESC, is_primary_producer ASC, name') + # TODO was sorted with is_distributor DESC as well, not sure why or how we want ot sort this now + @enterprises = Enterprise.managed_by(spree_current_user).order('is_primary_producer ASC, name') @product_count = Spree::Product.active.managed_by(spree_current_user).count @order_cycle_count = OrderCycle.active.managed_by(spree_current_user).count end diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index 4c9c32dbae..8561704686 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -2,14 +2,14 @@ require 'open_food_network/spree_api_key_loader' Spree::Admin::ProductsController.class_eval do include OpenFoodNetwork::SpreeApiKeyLoader - before_filter :load_form_data, :only => [:bulk_edit, :new, :edit] + before_filter :load_form_data, :only => [:bulk_edit, :new, :create, :edit, :update] before_filter :load_spree_api_key, :only => :bulk_edit alias_method :location_after_save_original, :location_after_save respond_to :json, :only => :clone - respond_override create: { html: { + respond_override create: { html: { success: lambda { if params[:button] == "add_another" redirect_to new_admin_product_path diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 0abfd783b5..22dd41549f 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -6,24 +6,6 @@ require 'open_food_network/order_grouper' require 'open_food_network/customers_report' Spree::Admin::ReportsController.class_eval do - # Fetches user's distributors, suppliers and order_cycles - before_filter :load_data, only: [:customers, :products_and_inventory] - - # Render a partial for orders and fulfillment description - respond_override :index => { :html => { :success => lambda { - @reports[:orders_and_fulfillment][:description] = - render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe - @reports[:products_and_inventory][:description] = - render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe - @reports[:customers][:description] = - render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe - } } } - - # OVERRIDING THIS so we use a method not a constant for available reports - def index - @reports = available_reports - respond_with(@reports) - end REPORT_TYPES = { orders_and_fulfillment: [ @@ -42,6 +24,26 @@ Spree::Admin::ReportsController.class_eval do ] } + # Fetches user's distributors, suppliers and order_cycles + before_filter :load_data, only: [:customers, :products_and_inventory] + + # Render a partial for orders and fulfillment description + respond_override :index => { :html => { :success => lambda { + @reports[:orders_and_fulfillment][:description] = + render_to_string(partial: 'orders_and_fulfillment_description', layout: false, locals: {report_types: REPORT_TYPES[:orders_and_fulfillment]}).html_safe + @reports[:products_and_inventory][:description] = + render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe + @reports[:customers][:description] = + render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe + } } } + + + # Overide spree reports list. + def index + @reports = authorized_reports + respond_with(@reports) + end + # This action is short because we refactored it like bosses def customers @report_types = REPORT_TYPES[:customers] @@ -592,28 +594,29 @@ Spree::Admin::ReportsController.class_eval do private def load_data + # Load distributors either owned by the user or selling their enterprises products. my_distributors = Enterprise.is_distributor.managed_by(spree_current_user) my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user) distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers)) @distributors = my_distributors | distributors_of_my_products + # Load suppliers either owned by the user or supplying products their enterprises distribute. suppliers_of_products_I_distribute = my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq @suppliers = my_suppliers | suppliers_of_products_I_distribute @order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC') end - def available_reports + def authorized_reports reports = { :orders_and_distributors => {:name => "Orders And Distributors", :description => "Orders with distributor details"}, :bulk_coop => {:name => "Bulk Co-Op", :description => "Reports for Bulk Co-Op orders"}, :payments => {:name => "Payment Reports", :description => "Reports for Payments"}, :orders_and_fulfillment => {:name => "Orders & Fulfillment Reports", :description => ''}, :customers => {:name => "Customers", :description => 'Customer details'}, - :products_and_inventory => {:name => "Products & Inventory", :description => ''} + :products_and_inventory => {:name => "Products & Inventory", :description => ''}, + :sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" } } - if spree_current_user.has_spree_role? 'admin' - reports[:sales_total] = { :name => "Sales Total", :description => "Sales Total For All Orders" } - end - reports + # Return only reports the user is authorized to view. + reports.select { |action| can? action, :report } end def total_units(line_items) diff --git a/app/controllers/spree/api/products_controller_decorator.rb b/app/controllers/spree/api/products_controller_decorator.rb index 65765e03ca..111f36962c 100644 --- a/app/controllers/spree/api/products_controller_decorator.rb +++ b/app/controllers/spree/api/products_controller_decorator.rb @@ -12,6 +12,7 @@ Spree::Api::ProductsController.class_eval do def bulk_products @products = OpenFoodNetwork::Permissions.new(current_api_user).managed_products. merge(product_scope). + order('created_at DESC'). ransack(params[:q]).result. page(params[:page]).per(params[:per_page]) diff --git a/app/helpers/admin/image_settings_helper.rb b/app/helpers/admin/image_settings_helper.rb new file mode 100644 index 0000000000..10c34fe372 --- /dev/null +++ b/app/helpers/admin/image_settings_helper.rb @@ -0,0 +1,21 @@ +module Admin + module ImageSettingsHelper + def admin_image_settings_format_options + [['Unchanged', ''], ['PNG', 'png'], ['JPEG', 'jpg']] + end + + def admin_image_settings_geometry_from_style(style) + geometry, format = admin_image_settings_split_style style + geometry + end + + def admin_image_settings_format_from_style(style) + geometry, format = admin_image_settings_split_style style + format + end + + def admin_image_settings_split_style(style) + [style, nil].flatten[0..1] + end + end +end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 7fec829fe7..094220b5a4 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -41,6 +41,12 @@ module Admin render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end + def admin_inject_enterprise_long_description + # Clean line breaks and quotes. + long_description = @enterprise.long_description.blank? ? "" : @enterprise.long_description.gsub("\r\n", "
").gsub("\"", """).gsub("'","'") + render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.enterprises', name: 'longDescription', json: "'#{long_description}'"} + end + def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) json = serializer.new(data).to_json diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 795dcb6a5c..046dd5d0eb 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -21,10 +21,14 @@ module InjectionHelper inject_json_ams "taxons", Spree::Taxon.all, Api::TaxonSerializer end + def inject_currency_config + inject_json_ams "currencyConfig", {}, Api::CurrencyConfigSerializer + end + def inject_spree_api_key render partial: "json/injection_ams", locals: {name: 'spreeApiKey', json: "'#{@spree_api_key.to_s}'"} end - + def inject_available_countries inject_json_ams "availableCountries", available_countries, Api::CountrySerializer end diff --git a/app/helpers/spree/orders_helper.rb b/app/helpers/spree/orders_helper.rb index b72fb43db5..93b59c8e4b 100644 --- a/app/helpers/spree/orders_helper.rb +++ b/app/helpers/spree/orders_helper.rb @@ -8,7 +8,7 @@ module Spree def order_distribution_subtotal(order, options={}) options.reverse_merge! :format_as_currency => true amount = order.adjustments.enterprise_fee.sum &:amount - options.delete(:format_as_currency) ? number_to_currency(amount) : amount + options.delete(:format_as_currency) ? spree_number_to_currency(amount) : amount end def alternative_available_distributors(order) diff --git a/app/helpers/spree/products_helper_decorator.rb b/app/helpers/spree/products_helper_decorator.rb index f918a47649..22740218e1 100644 --- a/app/helpers/spree/products_helper_decorator.rb +++ b/app/helpers/spree/products_helper_decorator.rb @@ -1,8 +1,9 @@ module Spree ProductsHelper.class_eval do - # Return the price of the variant + # Return the price of the variant, overriding sprees price diff capability. + # This will allways return the variant price as if the show_variant_full_price is set. def variant_price_diff(variant) - "(#{number_to_currency variant.price})" + "(#{Spree::Money.new(variant.price).to_s})" end diff --git a/app/helpers/spree_currency_helper.rb b/app/helpers/spree_currency_helper.rb new file mode 100644 index 0000000000..479ced2771 --- /dev/null +++ b/app/helpers/spree_currency_helper.rb @@ -0,0 +1,5 @@ +module SpreeCurrencyHelper + def spree_number_to_currency(amount) + Spree::Money.new(amount).to_s + end +end diff --git a/app/mailers/enterprise_mailer.rb b/app/mailers/enterprise_mailer.rb index 73c09e1e56..4e37d112e0 100644 --- a/app/mailers/enterprise_mailer.rb +++ b/app/mailers/enterprise_mailer.rb @@ -1,8 +1,16 @@ +require 'devise/mailers/helpers' class EnterpriseMailer < Spree::BaseMailer - def creation_confirmation(enterprise) - find_enterprise(enterprise) - subject = "#{@enterprise.name} is now on #{Spree::Config[:site_name]}" - mail(:to => @enterprise.owner.email, :from => from_address, :subject => subject) + include Devise::Mailers::Helpers + + def confirmation_instructions(record, token, opts={}) + @token = token + find_enterprise(record) + opts = { + subject: "Please confirm your email for #{@enterprise.name}", + to: [ @enterprise.owner.email, @enterprise.email ].uniq, + from: from_address, + } + devise_mail(record, :confirmation_instructions, opts) end private diff --git a/app/mailers/spree/order_mailer_decorator.rb b/app/mailers/spree/order_mailer_decorator.rb index cae4de0e79..a43399bd95 100644 --- a/app/mailers/spree/order_mailer_decorator.rb +++ b/app/mailers/spree/order_mailer_decorator.rb @@ -1,6 +1,7 @@ Spree::OrderMailer.class_eval do helper HtmlHelper helper CheckoutHelper + helper SpreeCurrencyHelper def confirm_email(order, resend = false) find_order(order) subject = (resend ? "[#{t(:resend).upcase}] " : '') diff --git a/app/mailers/spree/user_mailer_decorator.rb b/app/mailers/spree/user_mailer_decorator.rb index ff8bdc4691..dd21a3d0d7 100644 --- a/app/mailers/spree/user_mailer_decorator.rb +++ b/app/mailers/spree/user_mailer_decorator.rb @@ -1,5 +1,5 @@ Spree::UserMailer.class_eval do - + def signup_confirmation(user) @user = user mail(:to => user.email, :from => from_address, diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 537ac78295..0cc6fd19b9 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -1,12 +1,14 @@ class Enterprise < ActiveRecord::Base - TYPES = %w(full single profile) + SELLS = %w(none own any) ENTERPRISE_SEARCH_RADIUS = 100 + devise :confirmable, reconfirmable: true + self.inheritance_column = nil acts_as_gmappable :process_geocoding => false - after_create :send_creation_email + before_create :check_email has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup' has_many :producer_properties, foreign_key: 'producer_id' @@ -34,7 +36,7 @@ class Enterprise < ActiveRecord::Base path: 'public/images/enterprises/logos/:id/:style/:basename.:extension' has_attached_file :promo_image, - styles: { large: "1200x260#", thumb: "100x100>" }, + styles: { large: ["1200x260#", :jpg], medium: ["720x156#", :jpg], thumb: ["100x100>", :jpg] }, url: '/images/enterprises/promo_images/:id/:style/:basename.:extension', path: 'public/images/enterprises/promo_images/:id/:style/:basename.:extension' @@ -47,19 +49,23 @@ class Enterprise < ActiveRecord::Base validates :name, presence: true - validates :type, presence: true, inclusion: {in: TYPES} + validates :sells, presence: true, inclusion: {in: SELLS} validates :address, presence: true, associated: true + validates :email, presence: true validates_presence_of :owner - validate :enforce_ownership_limit, if: lambda { owner_id_changed? } + validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? } + validates_length_of :description, :maximum => 255 - before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? } + before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? } before_validation :set_unused_address_fields after_validation :geocode_address scope :by_name, order('name') scope :visible, where(:visible => true) + scope :confirmed, where('confirmed_at IS NOT NULL') + scope :unconfirmed, where('confirmed_at IS NULL') scope :is_primary_producer, where(:is_primary_producer => true) - scope :is_distributor, where(:is_distributor => true) + scope :is_distributor, where('sells != ?', 'none') scope :supplying_variant_in, lambda { |variants| joins(:supplied_products => :variants_including_master).where('spree_variants.id IN (?)', variants).select('DISTINCT enterprises.*') } scope :with_supplied_active_products_on_hand, lambda { joins(:supplied_products) @@ -210,6 +216,33 @@ class Enterprise < ActiveRecord::Base Spree::Variant.joins(:product => :product_distributions).where('product_distributions.distributor_id=?', self.id) end + def is_distributor + self.sells != "none" + end + + # Simplify enterprise categories for frontend logic and icons, and maybe other things. + def category + # Make this crazy logic human readable so we can argue about it sanely. + cat = self.is_primary_producer ? "producer_" : "non_producer_" + cat << "sells_" + self.sells + + # Map backend cases to front end cases. + case cat + when "producer_sells_any" + :producer_hub # Producer hub who sells own and others produce and supplies other hubs. + when "producer_sells_own" + :producer_shop # Producer with shopfront and supplies other hubs. + when "producer_sells_none" + :producer # Producer only supplies through others. + when "non_producer_sells_any" + :hub # Hub selling others products in order cycles. + when "non_producer_sells_own" + :hub # Wholesaler selling through own shopfront? Does this need a separate name? Should it exist? + when "non_producer_sells_none" + :hub_profile # Hub selling outside the system. + end + end + # Return all taxons for all distributed products def distributed_taxons Spree::Taxon. @@ -226,15 +259,20 @@ class Enterprise < ActiveRecord::Base select('DISTINCT spree_taxons.*') end + protected + + def devise_mailer + EnterpriseMailer + end private - def send_creation_email - EnterpriseMailer.creation_confirmation(self).deliver + def check_email + skip_confirmation! if owner.enterprises.confirmed.map(&:email).include?(email) end def strip_url(url) - url.andand.sub /(https?:\/\/)?/, '' + url.andand.sub(/(https?:\/\/)?/, '') end def set_unused_address_fields diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index cdc9d9932a..6e61b1a564 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -15,7 +15,7 @@ class EnterpriseGroup < ActiveRecord::Base path: 'public/images/enterprise_groups/logos/:id/:style/:basename.:extension' has_attached_file :promo_image, - styles: {large: "1200x260#"}, + styles: {large: ["1200x260#", :jpg]}, url: '/images/enterprise_groups/promo_images/:id/:style/:basename.:extension', path: 'public/images/enterprise_groups/promo_images/:id/:style/:basename.:extension' diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 79019f3e8a..1fb824d09c 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -1,27 +1,44 @@ class AbilityDecorator include CanCan::Ability + # All abilites are allocated from this initialiser, currently in 5 chunks. + # Spree also defines other abilities. def initialize(user) + add_base_abilities user if is_new_user? user add_enterprise_management_abilities user if can_manage_enterprises? user add_product_management_abilities user if can_manage_products? user + add_order_management_abilities user if can_manage_orders? user add_relationship_management_abilities user if can_manage_relationships? user end + # New users have no enterprises. + def is_new_user?(user) + user.enterprises.blank? + end + # Users can manage an enterprise if they have one. def can_manage_enterprises?(user) user.enterprises.present? end - + # Users can manage products if they have an enterprise. def can_manage_products?(user) - ( user.enterprises.map(&:type) & %w(single full) ).any? + can_manage_enterprises?(user) && user.enterprises.is_primary_producer.present? end + # Users can manage orders if they have a sells own/any enterprise. + def can_manage_orders?(user) + ( user.enterprises.map(&:sells) & %w(own any) ).any? + end def can_manage_relationships?(user) can_manage_enterprises? user end + # New users can create an enterprise, and gain other permissions from doing this. + def add_base_abilities(user) + can [:create], Enterprise + end def add_enterprise_management_abilities(user) # Spree performs authorize! on (:create, nil) when creating a new order from admin, and also (:search, nil) @@ -36,8 +53,13 @@ class AbilityDecorator can [:read, :edit, :update, :bulk_update], Enterprise do |enterprise| user.enterprises.include? enterprise end - end + # All enterprises can have fees, though possibly suppliers don't need them? + can [:index, :create], EnterpriseFee + can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee| + user.enterprises.include? enterprise_fee.enterprise + end + end def add_product_management_abilities(user) # Enterprise User can only access products that they are a supplier for @@ -57,6 +79,11 @@ class AbilityDecorator can [:admin, :index, :read, :search], Spree::Taxon can [:admin, :index, :read, :create, :edit], Spree::Classification + # Reports page + can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory], :report + end + + def add_order_management_abilities(user) # Enterprise User can only access orders that they are a distributor for can [:index, :create], Spree::Order can [:read, :update, :fire, :resend], Spree::Order do |order| @@ -64,25 +91,20 @@ class AbilityDecorator # during the order creation process from the admin backend order.distributor.nil? || user.enterprises.include?(order.distributor) end - can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor?) + can [:admin, :bulk_management], Spree::Order if user.admin? || user.enterprises.any?(&:is_distributor) can [:admin, :create], Spree::LineItem can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Payment can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Adjustment can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization - + can [:create], OrderCycle can [:admin, :index, :read, :edit, :update, :bulk_update, :clone], OrderCycle do |order_cycle| user.enterprises.include? order_cycle.coordinator end can [:for_order_cycle], Enterprise - can [:index, :create], EnterpriseFee - can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee| - user.enterprises.include? enterprise_fee.enterprise - end - can [:admin, :index, :read, :create, :edit, :update], ExchangeVariant can [:admin, :index, :read, :create, :edit, :update], Exchange can [:admin, :index, :read, :create, :edit, :update], ExchangeFee @@ -99,7 +121,7 @@ class AbilityDecorator end # Reports page - can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory], :report + can [:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], :report end diff --git a/app/models/spree/image_decorator.rb b/app/models/spree/image_decorator.rb new file mode 100644 index 0000000000..7d86aae3a6 --- /dev/null +++ b/app/models/spree/image_decorator.rb @@ -0,0 +1,23 @@ +Spree::Image.class_eval do + # Spree stores attachent definitions in JSON. This converts the style name and format to + # strings. However, when paperclip encounters these, it doesn't recognise the format. + # Here we solve that problem by converting format and style name to symbols. + # See also: ImageSettingsController decorator. + # + # eg. {'mini' => ['48x48>', 'png']} is converted to {mini: ['48x48>', :png]} + def self.format_styles(styles) + styles_a = styles.map do |name, style| + style[1] = style[1].to_sym if style.is_a? Array + [name.to_sym, style] + end + + Hash[styles_a] + end + + def self.reformat_styles + Spree::Image.attachment_definitions[:attachment][:styles] = + format_styles(Spree::Image.attachment_definitions[:attachment][:styles]) + end + + reformat_styles +end diff --git a/app/overrides/spree/admin/image_settings/edit/add_image_format.html.haml.deface b/app/overrides/spree/admin/image_settings/edit/add_image_format.html.haml.deface new file mode 100644 index 0000000000..deb79b5bbf --- /dev/null +++ b/app/overrides/spree/admin/image_settings/edit/add_image_format.html.haml.deface @@ -0,0 +1,11 @@ +/ replace_contents '#styles_list' + +- @styles.each_with_index do |(style_name, style_value), index| + .field.three.columns + = label_tag "attachment_styles[#{style_name}]", style_name + %a.destroy_style.with-tip{:alt => t(:destroy), :href => "#", :title => t(:destroy)} + %i.icon-trash + = text_field_tag "attachment_styles[#{style_name}][]", admin_image_settings_geometry_from_style(style_value), :class => 'fullwidth' + %br/ + - current_format = admin_image_settings_format_from_style(style_value) || '' + = select_tag "attachment_styles[#{style_name}][]", options_for_select(admin_image_settings_format_options, current_format), :class => 'fullwidth', :id => "attachment_styles_format_#{style_name}" diff --git a/app/overrides/spree/admin/payment_methods/index/remove_configuration_sidebar.deface b/app/overrides/spree/admin/payment_methods/index/remove_configuration_sidebar.deface new file mode 100644 index 0000000000..e4f7cbce3b --- /dev/null +++ b/app/overrides/spree/admin/payment_methods/index/remove_configuration_sidebar.deface @@ -0,0 +1 @@ +remove "code[erb-loud]:contains(\"render :partial => 'spree/admin/shared/configuration_menu'\")" \ No newline at end of file diff --git a/app/overrides/spree/layouts/admin/admin_inside_head.html.haml.deface b/app/overrides/spree/layouts/admin/admin_inside_head.html.haml.deface new file mode 100644 index 0000000000..1d77b5315d --- /dev/null +++ b/app/overrides/spree/layouts/admin/admin_inside_head.html.haml.deface @@ -0,0 +1,5 @@ +/ + insert_before '[data-hook="admin_inside_head"]' + += render "layouts/bugherd_script" +%link{'data-require' => "font-awesome@*", 'data-semver'=>"4.2.0", 'rel' => "stylesheet", 'href' => "//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css"} diff --git a/app/overrides/spree/layouts/admin/set_bugherd_in_backend.html.haml.deface b/app/overrides/spree/layouts/admin/set_bugherd_in_backend.html.haml.deface deleted file mode 100644 index ae7163c2f4..0000000000 --- a/app/overrides/spree/layouts/admin/set_bugherd_in_backend.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ - insert_before '[data-hook="admin_inside_head"]' - -= render "layouts/bugherd_script" \ No newline at end of file diff --git a/app/overrides/spree/layouts/spree_application/set_bugherd_in_frontend.html.haml.deface b/app/overrides/spree/layouts/spree_application/inside_head.html.haml.deface similarity index 100% rename from app/overrides/spree/layouts/spree_application/set_bugherd_in_frontend.html.haml.deface rename to app/overrides/spree/layouts/spree_application/inside_head.html.haml.deface diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index 80acf6d7c4..e6d00b7aae 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -1,3 +1,3 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer - attributes :name, :id, :is_primary_producer, :is_distributor, :payment_method_ids, :shipping_method_ids -end \ No newline at end of file + attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids +end diff --git a/app/serializers/api/currency_config_serializer.rb b/app/serializers/api/currency_config_serializer.rb new file mode 100644 index 0000000000..ab17af8715 --- /dev/null +++ b/app/serializers/api/currency_config_serializer.rb @@ -0,0 +1,32 @@ +class Api::CurrencyConfigSerializer < ActiveModel::Serializer + attributes :currency, :display_currency, :symbol, :symbol_position, :hide_cents, :decimal_mark, :thousands_separator + + def currency + Spree::Config[:currency] + end + + def display_currency + Spree::Config[:display_currency] + end + + def symbol + ::Money.new(1, Spree::Config[:currency]).symbol + end + + def symbol_position + Spree::Config[:currency_symbol_position] + end + + def hide_cents + Spree::Config[:hide_cents] + end + + def decimal_mark + Spree::Config[:currency_decimal_mark] + end + + def thousands_separator + Spree::Config[:currency_thousands_separator] + end + +end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index c76cffd7d1..a2aa506d2f 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -4,7 +4,7 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer end private - + def cached_serializer_hash Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash end @@ -17,9 +17,6 @@ end class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer attributes :orders_close_at, :active - #TODO: Remove these later - attributes :icon, :has_shopfront, :can_aggregate - def orders_close_at OrderCycle.first_closing_for(object).andand.orders_close_at end @@ -28,41 +25,7 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer @options[:active_distributors].andand.include? object end - # TODO: Move this back to uncached section when relavant properties are defined on the Enterprise model - def icon - # TODO: Replace with object.has_shopfront when this property exists - if has_shopfront - if can_aggregate - "/assets/map_005-hub.svg" - else - if object.is_distributor - "/assets/map_003-producer-shop.svg" - else - "/assets/map_001-producer-only.svg" - end - end - else - if can_aggregate - "/assets/map_006-hub-profile.svg" - else - if object.is_distributor - "/assets/map_004-producer-shop-profile.svg" - else - "/assets/map_002-producer-only-profile.svg" - end - end - end - end - # TODO: Remove this when flags on enterprises are switched over - def has_shopfront - object.type != 'profile' - end - - # TODO: Remove this when flags on enterprises are switched over - def can_aggregate - object.is_distributor && object.suppliers != [object] - end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer @@ -72,8 +35,8 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer attributes :name, :id, :description, :latitude, :longitude, :long_description, :website, :instagram, :linkedin, :twitter, :facebook, :is_primary_producer, :is_distributor, :phone, :visible, - :email, :hash, :logo, :promo_image, :path, - :pickup, :delivery + :email, :hash, :logo, :promo_image, :path, :pickup, :delivery, + :icon, :icon_font, :producer_icon_font, :category has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer has_many :supplied_taxons, serializer: Api::IdSerializer @@ -82,6 +45,10 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer + def visible + object.visible && object.confirmed? + end + def pickup object.shipping_methods.where(:require_ship_address => false).present? end @@ -111,4 +78,42 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer def path "/enterprises/#{object.to_param}/shop" end + + # Map svg icons. + def icon + icons = { + :hub => "/assets/map_005-hub.svg", + :hub_profile => "/assets/map_006-hub-profile.svg", + :producer_hub => "/assets/map_005-hub.svg", + :producer_shop => "/assets/map_003-producer-shop.svg", + :producer => "/assets/map_001-producer-only.svg", + } + icons[object.category] + end + + # Choose regular icon font for enterprises. + def icon_font + icon_fonts = { + :hub => "ofn-i_063-hub", + :hub_profile => "ofn-i_064-hub-reversed", + :producer_hub => "ofn-i_063-hub", + :producer_shop => "ofn-i_059-producer", + :producer => "ofn-i_059-producer", + } + icon_fonts[object.category] + end + + # Choose producer page icon font - yes, sadly its got to be different. + # This duplicates some code but covers the producer page edge case where + # producer-hub has a producer icon without needing to duplicate the category logic in angular. + def producer_icon_font + icon_fonts = { + :hub => "", + :hub_profile => "", + :producer_hub => "ofn-i_059-producer", + :producer_shop => "ofn-i_059-producer", + :producer => "ofn-i_059-producer", + } + icon_fonts[object.category] + end end diff --git a/app/serializers/api/payment_method_serializer.rb b/app/serializers/api/payment_method_serializer.rb index 6c2203083d..d62bc18154 100644 --- a/app/serializers/api/payment_method_serializer.rb +++ b/app/serializers/api/payment_method_serializer.rb @@ -1,3 +1,3 @@ class Api::PaymentMethodSerializer < ActiveModel::Serializer - attributes :name, :id, :method_type + attributes :name, :description, :id, :method_type end diff --git a/app/serializers/api/state_serializer.rb b/app/serializers/api/state_serializer.rb index bcf9221ec5..7a76e340b4 100644 --- a/app/serializers/api/state_serializer.rb +++ b/app/serializers/api/state_serializer.rb @@ -1,3 +1,7 @@ class Api::StateSerializer < ActiveModel::Serializer attributes :id, :name, :abbr + + def abbr + object.abbr.upcase + end end \ No newline at end of file diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index e03f1e0ec8..5871d6def4 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -1,12 +1,12 @@ class Api::VariantSerializer < ActiveModel::Serializer attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display, - :on_demand, :price, :fees, :base_price + :on_demand, :price, :fees, :price_with_fees - def price + def price_with_fees object.price_with_fees(options[:current_distributor], options[:current_order_cycle]) end - def base_price + def price object.price end diff --git a/app/serializers/spree/api/variant_serializer.rb b/app/serializers/spree/api/variant_serializer.rb index f594a540ef..cd31196ca0 100644 --- a/app/serializers/spree/api/variant_serializer.rb +++ b/app/serializers/spree/api/variant_serializer.rb @@ -7,6 +7,7 @@ class Spree::Api::VariantSerializer < ActiveModel::Serializer end def price + # Decimals are passed to json as strings, we need to run parseFloat.toFixed(2) on the client side. object.price.nil? ? 0.to_f : object.price end -end \ No newline at end of file +end diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 6f86c3e703..20bd174de9 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -32,13 +32,9 @@ .row .three.columns.alpha - %label Enterprise Type(s) - .with-tip{'data-powertip' => "Select 'Producer' if you are a primary producer of food. Select 'Hub' if you want a shop-front. You can choose either or both."} + %label Primary Producer + .with-tip{'data-powertip' => "Select 'Producer' if you are a primary producer of food."} %a What's this? - .two.columns - = f.check_box :is_distributor, 'ng-model' => 'Enterprise.is_distributor' -   - = f.label :is_distributor, 'Hub' .five.columns.omega = f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer'   @@ -47,21 +43,21 @@ .row .alpha.eleven.columns .three.columns.alpha - = f.label :type, 'Profile type' - .with-tip{'data-powertip' => "Full - enterprise may have products and relationships.
Single - enterprise may have products but no relationships.
Profile - enterprise has a profile but no products or relationships.
"} + = f.label :sells, 'Sells' + .with-tip{'data-powertip' => "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
"} %a What's this? .two.columns - = f.radio_button :type, "full" + = f.radio_button :sells, "none", 'ng-model' => 'Enterprise.sells'   - = f.label :type, "Full", value: "full" + = f.label :sells, "None", value: "none" .two.columns - = f.radio_button :type, "single" + = f.radio_button :sells, "own", 'ng-model' => 'Enterprise.sells'   - = f.label :type, "Single", value: "single" + = f.label :sells, "Own", value: "own" .four.columns.omega - = f.radio_button :type, "profile" + = f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells'   - = f.label :type, "Profile", value: "profile" + = f.label :sells, "Any", value: "any" .row .three.columns.alpha %label Visible in search? @@ -177,12 +173,19 @@ .alpha.three.columns = f.label :description, 'Short Description' .omega.eight.columns - = f.text_field :description, placeholder: 'Tell us about your enterprise in one or two sentences' + = f.text_field :description, maxlength: 255, placeholder: 'Tell us about your enterprise in one or two sentences' .row .alpha.three.columns = f.label :long_description, 'About Us' .omega.eight.columns - = f.text_area :long_description, rows: 6, placeholder: 'Tell us about yourself. This information appears on your public profile (under "About Us")', class: 'fullwidth' + -# textAngular toolbar options, add to the ta-toolbar array below and separate into groups with extra ],[ if needed: + -# ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'quote'], + -# ['bold', 'italics', 'underline', 'strikeThrough', 'ul', 'ol', 'redo', 'undo', 'clear'], + -# ['justifyLeft','justifyCenter','justifyRight','indent','outdent'], + -# ['html', 'insertImage', 'insertLink', 'insertVideo'] + %text-angular{'ng-model' => 'htmlVariable', 'id' => 'enterprise_long_description', 'name' => 'enterprise[long_description]', 'class' => 'text-angular', + 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", + 'placeholder' => 'Tell customers about yourself. This information appears on your public profile.'} %fieldset.eleven.columns.alpha.no-border-bottom %legend IMAGES .row diff --git a/app/views/admin/enterprises/_ng_form.html.haml b/app/views/admin/enterprises/_ng_form.html.haml index 0ccd963d31..e3ebe95877 100644 --- a/app/views/admin/enterprises/_ng_form.html.haml +++ b/app/views/admin/enterprises/_ng_form.html.haml @@ -1,8 +1,9 @@ = admin_inject_enterprise += admin_inject_enterprise_long_description = admin_inject_payment_methods = admin_inject_shipping_methods -.sixteen.columns.alpha{ ng: { app: 'admin.enterprises', controller: 'enterpriseCtrl' } } +.sixteen.columns.alpha{ ng: { app: 'admin.enterprises', controller: 'enterpriseCtrl' }, nav: { check: '', callback: 'enterpriseNavCallback()' }} .eleven.columns.alpha = render 'form', f: f .one.column   diff --git a/app/views/admin/enterprises/_sidebar.html.haml b/app/views/admin/enterprises/_sidebar.html.haml index ac4966fa80..a4c149c9e9 100644 --- a/app/views/admin/enterprises/_sidebar.html.haml +++ b/app/views/admin/enterprises/_sidebar.html.haml @@ -1,6 +1,6 @@ +- if can? :admin, EnterpriseFee + = render 'sidebar_enterprise_fees', f: f - if can? :admin, Spree::PaymentMethod = render 'sidebar_payment_methods', f: f - if can? :admin, Spree::ShippingMethod = render 'sidebar_shipping_methods', f: f -- if can? :admin, EnterpriseFee - = render 'sidebar_enterprise_fees', f: f diff --git a/app/views/admin/enterprises/_sidebar_enterprise_fees.html.haml b/app/views/admin/enterprises/_sidebar_enterprise_fees.html.haml index 50de7e07d9..566d87b832 100644 --- a/app/views/admin/enterprises/_sidebar_enterprise_fees.html.haml +++ b/app/views/admin/enterprises/_sidebar_enterprise_fees.html.haml @@ -1,5 +1,5 @@ - enterprise_fees_color = @enterprise_fees.count > 0 ? "blue" : "red" -.sidebar_item.four.columns.alpha#enterprise_fees{ ng: { show: 'Enterprise.is_distributor' } } +.sidebar_item.four.columns.alpha#enterprise_fees{ ng: { show: 'Enterprise.category != "producer_hub"' } } .four.columns.alpha.header{ class: "#{enterprise_fees_color}" } %span.four.columns.alpha.centered Enterprise Fees .four.columns.alpha.list{ class: "#{enterprise_fees_color}" } diff --git a/app/views/admin/enterprises/_sidebar_payment_methods.html.haml b/app/views/admin/enterprises/_sidebar_payment_methods.html.haml index 8994c60be5..2d8a174e5c 100644 --- a/app/views/admin/enterprises/_sidebar_payment_methods.html.haml +++ b/app/views/admin/enterprises/_sidebar_payment_methods.html.haml @@ -1,4 +1,4 @@ -.sidebar_item.four.columns.alpha#payment_methods{ ng: { show: 'Enterprise.is_distributor' } } +.sidebar_item.four.columns.alpha#payment_methods{ ng: { show: 'Enterprise.sells != "none"' } } .four.columns.alpha.header{ ng: { class: "paymentMethodsColor()" } } %span.four.columns.alpha.centered Payment Methods .four.columns.alpha.list{ ng: { class: "paymentMethodsColor()" } } @@ -6,10 +6,13 @@ -# = hidden_field_tag "enterprise[payment_method_ids][]", [] - @payment_methods.each do |payment_method| %span.four.columns.alpha.list-item{ class: "#{cycle('odd','even')}", ng: { controller: 'paymentMethodCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } } - %a.three.columns.alpha{ href: "#{edit_admin_payment_method_path(payment_method)}" } - = payment_method.name - %span.one.column.omega - = f.check_box :payment_method_ids, { multiple: true, 'ng-model' => 'PaymentMethod.selected' }, payment_method.id, nil + %span.four.columns + %span.three.columns.alpha + %label + = f.check_box :payment_method_ids, { multiple: true, 'ng-model' => 'PaymentMethod.selected' }, payment_method.id, nil + = payment_method.name + %a.one.columns.omega{ href: "#{edit_admin_payment_method_path(payment_method)}" } + %span.icon-arrow-right - else .four.columns.alpha.list-item %span.three.columns.alpha None Available diff --git a/app/views/admin/enterprises/_sidebar_shipping_methods.html.haml b/app/views/admin/enterprises/_sidebar_shipping_methods.html.haml index 6d4a858366..3e9a0434c0 100644 --- a/app/views/admin/enterprises/_sidebar_shipping_methods.html.haml +++ b/app/views/admin/enterprises/_sidebar_shipping_methods.html.haml @@ -1,14 +1,17 @@ -.sidebar_item.four.columns.alpha#shipping_methods{ ng: { show: 'Enterprise.is_distributor' } } +.sidebar_item.four.columns.alpha#shipping_methods{ ng: { show: 'Enterprise.sells != "none"' } } .four.columns.alpha.header{ ng: { class: "shippingMethodsColor()" } } %span.four.columns.alpha.centered Shipping Methods .four.columns.alpha.list{ ng: { class: "shippingMethodsColor()" } } - if @shipping_methods.count > 0 - @shipping_methods.each do |shipping_method| %span.four.columns.alpha.list-item{ class: "#{cycle('odd','even')}", ng: { controller: 'shippingMethodCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } } - %a.three.columns.alpha{ href: "#{edit_admin_shipping_method_path(shipping_method)}" } - = shipping_method.name - %span.one.column.omega - = f.check_box :shipping_method_ids, { :multiple => true, 'ng-model' => 'ShippingMethod.selected' }, shipping_method.id, nil + %span.four.columns + %span.three.columns.alpha + %label + = f.check_box :shipping_method_ids, { :multiple => true, 'ng-model' => 'ShippingMethod.selected' }, shipping_method.id, nil + = shipping_method.name + %a.one.columns.omega{ href: "#{edit_admin_shipping_method_path(shipping_method)}" } + %span.one.column.alpha.icon-arrow-right - else .four.columns.alpha.list-item %span.three.columns.alpha None Available diff --git a/app/views/admin/enterprises/edit.html.haml b/app/views/admin/enterprises/edit.html.haml index 6a8d48b24b..3c132bf5e2 100644 --- a/app/views/admin/enterprises/edit.html.haml +++ b/app/views/admin/enterprises/edit.html.haml @@ -4,7 +4,7 @@ Editing: = @enterprise.name -= form_for [main_app, :admin, @enterprise] do |f| += form_for [main_app, :admin, @enterprise], html: { "ng-app" => 'admin.enterprises', "ng-submit" => "navClear()", "ng-controller" => 'enterpriseCtrl' , "nav-check" => '', "nav-callback" => 'enterpriseNavCallback()' } do |f| = render 'ng_form', f: f .twelve.columns.alpha = render partial: 'spree/admin/shared/edit_resource_links' diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 44008b3bd2..ee1bdde996 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -8,7 +8,7 @@ = render 'admin/shared/enterprises_sub_menu' -= form_for @enterprise_set, :url => main_app.bulk_update_admin_enterprises_path do |f| += form_for @enterprise_set, url: main_app.bulk_update_admin_enterprises_path do |f| %table#listing_enterprises.index %colgroup %col{style: "width: 25%;"}/ @@ -16,16 +16,18 @@ %col{style: "width: 5%;"}/ - if spree_current_user.admin? %col{style: "width: 12%;"}/ - %col{style: "width: 18%;"}/ + - if spree_current_user.admin? + %col{style: "width: 18%;"}/ %col{style: "width: 25%;"}/ %thead %tr{"data-hook" => "enterprises_header"} %th Name %th Role + - if spree_current_user.admin? + %th Sells %th Visible? - if spree_current_user.admin? - %th Type - %th Owner + %th Owner %th %tbody = f.fields_for :collection do |enterprise_form| @@ -35,13 +37,11 @@ %td = enterprise_form.check_box :is_primary_producer Producer - %br/ - = enterprise_form.check_box :is_distributor - Hub + - if spree_current_user.admin? + %td= enterprise_form.select :sells, Enterprise::SELLS, {}, class: 'select2 fullwidth' %td= enterprise_form.check_box :visible - if spree_current_user.admin? - %td= enterprise_form.select :type, Enterprise::TYPES, {}, class: 'select2 fullwidth' - %td= enterprise_form.select :owner_id, enterprise.users.map{ |e| [ e.email, e.id ] }, {}, class: "select2 fullwidth" + %td= enterprise_form.select :owner_id, enterprise.users.map{ |e| [ e.email, e.id ] }, {}, class: "select2 fullwidth" %td{"data-hook" => "admin_users_index_row_actions"} = render 'actions', enterprise: enterprise - if @enterprises.empty? diff --git a/app/views/admin/enterprises/new.html.haml b/app/views/admin/enterprises/new.html.haml index 32f6ea0a8d..3df3551f47 100644 --- a/app/views/admin/enterprises/new.html.haml +++ b/app/views/admin/enterprises/new.html.haml @@ -3,7 +3,7 @@ - content_for :page_title do New Enterprise -= form_for [main_app, :admin, @enterprise] do |f| - = render partial: 'ng_form', :locals => { f: f } += form_for [main_app, :admin, @enterprise], html: { "ng-app" => 'admin.enterprises', "ng-submit" => "navClear()", "ng-controller" => 'enterpriseCtrl' , "nav-check" => '', "nav-callback" => 'enterpriseNavCallback()' } do |f| + = render 'ng_form', f: f .twelve.columns.alpha = render partial: 'spree/admin/shared/new_resource_links' diff --git a/app/views/admin/order_cycles/_row.html.haml b/app/views/admin/order_cycles/_row.html.haml index 75e84de025..3b8c6f34bc 100644 --- a/app/views/admin/order_cycles/_row.html.haml +++ b/app/views/admin/order_cycles/_row.html.haml @@ -5,12 +5,12 @@ %td= order_cycle_form.text_field :orders_open_at, :class => 'datetimepicker', :value => order_cycle.orders_open_at %td= order_cycle_form.text_field :orders_close_at, :class => 'datetimepicker', :value => order_cycle.orders_close_at %td.suppliers - - order_cycle.suppliers.managed_by(spree_current_user).each do |s| + - order_cycle.suppliers.merge(OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises).each do |s| = s.name %br/ %td= order_cycle.coordinator.name %td.distributors - - order_cycle.distributors.managed_by(spree_current_user).each do |d| + - order_cycle.distributors.merge(OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises).each do |d| = d.name %br/ diff --git a/app/views/admin/order_cycles/index.html.haml b/app/views/admin/order_cycles/index.html.haml index 1fac06f507..f9f832d83d 100644 --- a/app/views/admin/order_cycles/index.html.haml +++ b/app/views/admin/order_cycles/index.html.haml @@ -5,8 +5,6 @@ %li#new_order_cycle_link = button_link_to "New Order Cycle", main_app.new_admin_order_cycle_path, :icon => 'icon-plus', :id => 'admin_new_order_cycle_link' - - = form_for @order_cycle_set, :url => main_app.bulk_update_admin_order_cycles_path do |f| %table.index#listing_order_cycles %colgroup diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml index b655659738..21dca1542c 100644 --- a/app/views/checkout/_payment.html.haml +++ b/app/views/checkout/_payment.html.haml @@ -17,6 +17,7 @@ %em %small {{ Checkout.paymentMethod().name }} + %small .small-4.medium-2.columns.text-right %span.accordion-up %em @@ -29,16 +30,20 @@ -# TODO render this in Angular instead of server-side -# The problem being how to render the partials - - current_order.available_payment_methods.each do |method| - .row - .small-12.columns - %label - = radio_button_tag "order[payments_attributes][][payment_method_id]", method.id, false, - required: true, - "ng-model" => "order.payment_method_id" - = method.name + .row + .small-6.columns + - current_order.available_payment_methods.each do |method| + .row + .small-12.columns + %label + = radio_button_tag "order[payments_attributes][][payment_method_id]", method.id, false, + required: true, + "ng-model" => "order.payment_method_id" + = method.name - .row{"ng-if" => "order.payment_method_id == #{method.id}"} - .small-12.columns - = render partial: "spree/checkout/payment/#{method.method_type}", :locals => { :payment_method => method } + .row{"ng-if" => "order.payment_method_id == #{method.id}"} + .small-12.columns + = render partial: "spree/checkout/payment/#{method.method_type}", :locals => { :payment_method => method } + .small-6.columns + %small {{ Checkout.paymentMethod().description }} diff --git a/app/views/checkout/_shipping.html.haml b/app/views/checkout/_shipping.html.haml index 27089527a2..5ba5ed15e4 100644 --- a/app/views/checkout/_shipping.html.haml +++ b/app/views/checkout/_shipping.html.haml @@ -83,4 +83,4 @@ .row .small-12.columns.text-right - %button.primary{"ng-disabled" => "shipping.$invalid", "ng-click" => "next($event)", "ofn-focus" => "accordion['shipping']"} Next + %button.primary{"ng-disabled" => "shipping.$invalid", "ng-click" => "next($event)"} Next diff --git a/app/views/checkout/_summary.html.haml b/app/views/checkout/_summary.html.haml index 0e03af7413..8609fd7a16 100644 --- a/app/views/checkout/_summary.html.haml +++ b/app/views/checkout/_summary.html.haml @@ -5,7 +5,7 @@ %table %tr %th Cart total - %td.cart-total.text-right= number_to_currency checkout_cart_total_with_adjustments(current_order) + %td.cart-total.text-right= spree_number_to_currency(checkout_cart_total_with_adjustments(current_order)) - checkout_adjustments_for_summary(current_order, exclude: [:shipping, :distribution]).each do |adjustment| %tr @@ -14,11 +14,11 @@ %tr %th Shipping - %td.shipping.text-right {{ Checkout.shippingPrice() | currency }} + %td.shipping.text-right {{ Checkout.shippingPrice() | localizeCurrency }} %tr %th Total - %td.total.text-right {{ Checkout.cartTotal() | currency }} + %td.total.text-right {{ Checkout.cartTotal() | localizeCurrency }} - if current_order.price_adjustment_totals.present? - current_order.price_adjustment_totals.each do |label, total| %tr diff --git a/app/views/enterprise_mailer/confirmation_instructions.html.haml b/app/views/enterprise_mailer/confirmation_instructions.html.haml new file mode 100644 index 0000000000..19bab3f5ee --- /dev/null +++ b/app/views/enterprise_mailer/confirmation_instructions.html.haml @@ -0,0 +1,100 @@ +/ ORIGINAL & UGLY: +/ %p= "Welcome #{@resource.contact}!" +/ %p= "Please confirm your email address for #{@resource.name}." +/ %p= "Click the link below to activate your enterprise:" +/ %p= link_to 'Confirm this email address', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) + + +%html{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;", :xmlns => "http://www.w3.org/1999/xhtml"} + %head{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + / If you delete this meta tag, Half Life 3 will never be released. + %meta{:content => "width=device-width", :name => "viewport", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"}/ + %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"}/ + %title{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} Open Food Network + %link{:href => "http://rohanmitchell.com/random/template/basic-email-template/stylesheets/email.css", :rel => "stylesheet", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;", :type => "text/css"}/ + %body{:bgcolor => "#FFFFFF", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-webkit-text-size-adjust: none;height: 100%;width: 100%!important;"} + / HEADER + %table.head-wrap{:bgcolor => "#333333", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td.header.container{:style => "margin: 0 auto!important;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;display: block!important;max-width: 600px!important;clear: both!important;"} + .content{:style => "margin: 0 auto;padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;max-width: 600px;display: block;"} + %table{:bgcolor => "#333333", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %img{:src => "https://openfoodnetwork.org.au/assets/ofn_logo_beta-8e4dfc79deb25def2d107dea52dce492.png", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;max-width: 100%;", :width => "200"}/ + %td{:align => "right", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %h6.collapse{:style => "margin: 0!important;padding: 0;font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;line-height: 1.1;margin-bottom: 15px;color: #999;font-weight: 900;font-size: 14px;text-transform: uppercase;"} Open Food Network + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + / /HEADER + / BODY + %table.body-wrap{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td.container{:bgcolor => "#FFFFFF", :style => "margin: 0 auto!important;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;display: block!important;max-width: 600px!important;clear: both!important;"} + .content{:style => "margin: 0 auto;padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;max-width: 600px;display: block;"} + %table{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %h3{:style => "margin: 0;padding: 0;font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;line-height: 1.1;margin-bottom: 15px;color: #000;font-weight: 500;font-size: 27px;"}= "Welcome, #{@resource.contact}!" + %p.lead{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 10px;font-weight: normal;font-size: 17px;line-height: 1.6;"} + = "Please confirm email address for your enterprise " + %strong + = "#{@resource.name}." + %p   + / Callout Panel + %p.callout{:style => "margin: 0; padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 15px;font-weight: normal;font-size: 14px;line-height: 1.6;background-color: #e1f0f5;"} + = "Click the link below to confirm email and to activate your enterprise. This link can be used only once:" + %br + %strong + = link_to 'Confirm this email address »', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) + / /Callout Panel + %p   + %p{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 10px;font-weight: normal;font-size: 14px;line-height: 1.6;"}= "We're so excited that you're joining the Open Food Network! Don't hestitate to get in touch if you have any questions." + %p   + / social & contact + %table.social{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;background-color: #ebebeb;width: 100%;", :width => "100%"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + / column 1 + %table.column{:align => "left", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 280px;float: left;min-width: 279px;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %h5{:style => "margin: 0;padding: 0;font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;line-height: 1.1;margin-bottom: 15px;color: #000;font-weight: 900;font-size: 17px;"} Connect with Us: + %p{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 10px;font-weight: normal;font-size: 14px;line-height: 1.6;"} + %a.soc-btn.fb{:href => "https://www.facebook.com/OpenFoodNet", :style => "margin: 0;padding: 3px 7px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #FFF;font-size: 12px;margin-bottom: 10px;text-decoration: none;font-weight: bold;display: block;text-align: center;background-color: #3B5998!important;", :target => "_blank"} Facebook + %a.soc-btn.tw{:href => "https://twitter.com/OpenFoodNet", :style => "margin: 0;padding: 3px 7px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #FFF;font-size: 12px;margin-bottom: 10px;text-decoration: none;font-weight: bold;display: block;text-align: center;background-color: #1daced!important;", :target => "_blank"} Twitter + %a.soc-btn.li{:href => "http://www.linkedin.com/groups/Open-Food-Foundation-4743336", :style => "margin: 0;padding: 3px 7px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #FFF;font-size: 12px;margin-bottom: 10px;text-decoration: none;font-weight: bold;display: block;text-align: center;background-color: #0073b2!important;", :target => "_blank"} LinkedIn + / /column 1 + / column 2 + %table.column{:align => "left", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 280px;float: left;min-width: 279px;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %h5{:style => "margin: 0;padding: 0;font-family: \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, Arial, \"Lucida Grande\", sans-serif;line-height: 1.1;margin-bottom: 15px;color: #000;font-weight: 900;font-size: 17px;"} Email us: + %p{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 10px;font-weight: normal;font-size: 14px;line-height: 1.6;"} + %strong{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %a{:href => "hello@openfoodnetwork.org", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #0096ad;"} hello@openfoodnetwork.org + / /column 2 + %span.clear{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;display: block;clear: both;"} + / /social & contact + / /content + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + / /BODY + / FOOTER + %table.footer-wrap{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;clear: both!important;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td.container{:style => "margin: 0 auto!important;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;display: block!important;max-width: 600px!important;clear: both!important;"} + / content + .content{:style => "margin: 0 auto;padding: 15px;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;max-width: 600px;display: block;"} + %table{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;width: 100%;"} + %tr{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %td{:align => "center", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + %p{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;margin-bottom: 10px;font-weight: normal;font-size: 14px;line-height: 1.6;"} + %a{:href => "https://openfoodnetwork.org.au/Terms-of-service.pdf", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #0096ad;", :target => "_blank"} Terms of service + | + %a{:href => "http://www.openfoodnetwork.org.au", :style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;color: #0096ad;", :target => "_blank"} Open Food Network + / | Unsubscribe + / /content + %td{:style => "margin: 0;padding: 0;font-family: \"Helvetica Neue\", \"Helvetica\", Helvetica, Arial, sans-serif;"} + / /FOOTER diff --git a/app/views/enterprise_mailer/creation_confirmation.html.haml b/app/views/enterprise_mailer/creation_confirmation.html.haml deleted file mode 100644 index 0df3bf06a0..0000000000 --- a/app/views/enterprise_mailer/creation_confirmation.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -%h1 - = @enterprise.name + " has been created" - -%h3 - Why not check it out on - %a{ href: "#{map_url}" } - = Spree::Config[:site_name] + "?" - -If you have any questions, please get in touch with us at: hello@openfoodnetwork.org diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml index b033c93d12..1aa5b02629 100644 --- a/app/views/groups/index.html.haml +++ b/app/views/groups/index.html.haml @@ -40,8 +40,9 @@ %h5 Our hubs & producers %ul.small-block-grid-2 %li{"ng-repeat" => "enterprise in group.enterprises", "scroll-after-load" => true} - %hub-modal{"ng-if" => "enterprise.is_distributor"} - %producer-modal{"ng-if" => "!enterprise.is_distributor", "show-hub-actions" => 'true'} + %enterprise-modal{"ng-if" => "enterprise.is_distributor"} + {{ enterprise.name }} + %enterprise-modal{"ng-if" => "!enterprise.is_distributor", "show-hub-actions" => 'true'} {{ enterprise.name }} diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index 406431b9b7..b3741e5aec 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -23,7 +23,7 @@ %label Our producers %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2 %li{"ng-repeat" => "enterprise in hub.producers"} - %producer-modal + %enterprise-modal %i.ofn-i_036-producers %span {{ enterprise.name }} diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml index 78d755f1bc..52fceb6688 100644 --- a/app/views/home/_filters.html.haml +++ b/app/views/home/_filters.html.haml @@ -1,23 +1,22 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + = render partial: 'shared/components/show_profiles' .row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns + .small-12.columns .row.filter-box .small-12.large-9.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5 - %taxon-selector{objects: "hubs | hubs:query", + %taxon-selector{objects: "Enterprises.hubs | searchEnterprises:query", results: "activeTaxons"} .small-12.large-3.columns - %h5.tdhead + %h5.tdhead .light Filter by Delivery %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2 - %shipping-type-selector{results: "shippingTypes"} - .row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} - .small-12.columns - %a.button.secondary.small.expand{"ng-click" => "clearAll()"} - %i.ofn-i_009-close - Clear all filters + %shipping-type-selector{results: "shippingTypes"} + += render partial: 'shared/components/filter_box' diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index ff90a45d48..1e5151469e 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -1,40 +1,22 @@ = inject_enterprises -#hubs.hubs{"ng-controller" => "HubsCtrl"} +#hubs.hubs{"ng-controller" => "EnterprisesCtrl"} .row .small-12.columns - %h1 Shop your local area - / %div - / Shop a - / %ofn-modal{title: "food hub"} - / = render partial: "modals/food_hub" - / from the list below: - - #active-table-search.row.pad-top - .small-12.columns - / %i.ofn-i_020-search - %input{type: :text, - "ng-model" => "query", - placeholder: "Search by name or suburb...", - "ng-debounce" => "150", - "ofn-disable-enter" => true} + %h1 Shop in your local area + = render partial: "shared/components/enterprise_search" = render partial: "home/filters" .row{bindonce: true} .small-12.columns .active_table - %hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredHubs = (hubs | hubs:query | taxons:activeTaxons | shipping:shippingTypes)", - "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", + %hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredEnterprises = (Enterprises.hubs | visible | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])", + "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "scroll-after-load" => true, "ng-controller" => "HubNodeCtrl", - id: "{{hub.hash}}"} + id: "{{hub.hash}}"} .small-12.columns = render partial: 'home/skinny' = render partial: 'home/fat' - .row{"ng-show" => "filteredHubs.length == 0"} - .columns.small-12 - %p.no-results - Sorry, no results found for - %strong {{query}}. - Try another search? + = render partial: 'shared/components/enterprise_no_results' diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index da9709479b..7100a06974 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,12 +1,10 @@ -.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}", bindonce: true} +.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true} + .columns.small-12.medium-6.large-5.skinny-head %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} - %i{ ng: { class: "{ 'ofn-i_063-hub': hub.can_aggregate && hub.has_shopfront, - 'ofn-i_059-producer': !hub.can_aggregate && hub.has_shopfront, - 'ofn-i_060-producer-reversed': !hub.can_aggregate && !hub.has_shopfront, - 'ofn-i_064-hub-reversed': hub.can_aggregate && !hub.has_shopfront }" } } - / %i.ofn-i_063-hub + %i{ng: {class: "hub.icon_font"}} %span.margin-top.hub-name-listing {{ hub.name | truncate:40}} + .columns.small-4.medium-2.large-2 %span.margin-top {{ hub.address.city }} .columns.small-2.medium-1.large-1 @@ -15,16 +13,28 @@ .columns.small-6.medium-3.large-4.text-right{"bo-if" => "hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_033-open-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ bo: { if: "current()" } } %em Shopping here %span.margin-top{ bo: { if: "!current()" } } {{ hub.orders_close_at | sensible_timeframe }} .columns.small-6.medium-3.large-4.text-right{"bo-if" => "!hub.active"} %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"} %i.ofn-i_032-closed-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ bo: { if: "current()" } } %em Shopping here %span.margin-top{ bo: { if: "!current()" } } Orders closed +.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}", "ofn-empties-cart" => "hub"} + %i{ng: {class: "hub.icon_font"}} + %span.margin-top.hub-name-listing {{ hub.name | truncate:40}} + .columns.small-4.medium-2.large-2 + %span.margin-top {{ hub.address.city }} + .columns.small-2.medium-1.large-1 + %span.margin-top {{ hub.address.state_name | uppercase }} + .columns.small-6.medium-3.large-4.text-right + %span.margin-top{ bo: { if: "!current()" } } + %em Profile only diff --git a/app/views/json/partials/_enterprise.rabl b/app/views/json/partials/_enterprise.rabl index 5b52898482..b8800e22ae 100644 --- a/app/views/json/partials/_enterprise.rabl +++ b/app/views/json/partials/_enterprise.rabl @@ -21,9 +21,9 @@ node :promo_image do |enterprise| end node :icon do |e| - if e.is_primary_producer? and e.is_distributor? + if e.is_primary_producer and e.is_distributor image_path "map_003-producer-shop.svg" - elsif e.is_primary_producer? + elsif e.is_primary_producer image_path "map_001-producer-only.svg" else image_path "map_005-hub.svg" diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 07b0db8dfa..0c6ec608d4 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -29,6 +29,7 @@ = inject_json "railsFlash", "flash" = inject_taxons = inject_current_order + = inject_currency_config .off-canvas-wrap{offcanvas: true} .inner-wrap diff --git a/app/views/layouts/registration.html.haml b/app/views/layouts/registration.html.haml index 8946a27de1..d6122cdc3b 100644 --- a/app/views/layouts/registration.html.haml +++ b/app/views/layouts/registration.html.haml @@ -31,6 +31,6 @@ %section{ role: "main" } = yield - + #footer %loading diff --git a/app/views/modals/_producer.html.haml b/app/views/modals/_producer.html.haml deleted file mode 100644 index 51a45cfe5f..0000000000 --- a/app/views/modals/_producer.html.haml +++ /dev/null @@ -1,56 +0,0 @@ -%ofn-modal{title: "{{enterprise.name}}"} - .highlight - .highlight-top - %p.right - {{ [enterprise.address.city, enterprise.address.state_name] | printArray}} - %h3 - %i.ofn-i_036-producers - {{ enterprise.name }} - %img.hero-img{"ng-src" => "{{enterprise.promo_image}}"} - - .row{bindonce: true} - .small-12.large-8.columns - %div{"ng-show" => "enterprise.long_description.length > 0 || enterprise.logo"} - %p.modal-header About - .about-container - %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"} - %p.text-small{"ng-bind-html" => "enterprise.long_description"} - - .small-12.large-4.columns - %div.modal-centered{"bo-if" => "enterprise.email || enterprise.website || enterprise.phone"} - %p.modal-header Contact - %p{"bo-if" => "enterprise.phone"} - {{ enterprise.phone }} - - %p{"bo-if" => "enterprise.email"} - %a{"ng-href" => "{{enterprise.email | stripUrl}}", target: "_blank", mailto: true } - %span.email - {{ enterprise.email | stripUrl }} - - %p{"bo-show" => "enterprise.website"} - %a{"ng-href" => "http://{{enterprise.website}}", target: "_blank" } - {{ enterprise.website | stripUrl }} - - %div.modal-centered{"bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"} - %p.modal-header Follow - .follow-icons{bindonce: true} - %span{"bo-show" => "enterprise.twitter"} - %a{"ng-href" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"} - %i.ofn-i_041-twitter - - %span{"bo-show" => "enterprise.facebook"} - %a{"ng-href" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"} - %i.ofn-i_044-facebook - - %span{"bo-show" => "enterprise.linkedin"} - %a{"ng-href" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"} - %i.ofn-i_042-linkedin - - %span{"bo-show" => "enterprise.instagram"} - %a{"ng-href" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"} - %i.ofn-i_043-instagram - - %a.close-reveal-modal{"ng-click" => "$close()"} - %i.ofn-i_009-close - - diff --git a/app/views/producers/_filters.html.haml b/app/views/producers/_filters.html.haml index 63ccef2f0c..00472dc91c 100644 --- a/app/views/producers/_filters.html.haml +++ b/app/views/producers/_filters.html.haml @@ -1,18 +1,15 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + .small-12.medium-6.columns.text-right +   .row.animate-show{"ng-show" => "filtersActive"} .small-12.columns .row.filter-box .small-12.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6 - %taxon-selector{objects: "Producers.visible | filterProducers:query", + %taxon-selector{objects: "Enterprises.producers | searchEnterprises:query ", results: "activeTaxons"} - - .row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} - .small-12.columns - %a.button.secondary.small.expand{"ng-click" => "clearAll()"} - %i.ofn-i_009-close - Clear all filters diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index a5d6b790fc..aaa59b20e8 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -1,9 +1,16 @@ -.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}"} +.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} .columns.small-12.medium-4.large-4.skinny-head - / This needs logic to show profile only icon when available %i.ofn-i_060-producer-reversed - %i.ofn-i_059-producer - %span.margin-top - %strong {{ producer.name }} + %span{"bo-if" => "producer.is_distributor" } + %a.is_distributor{"bo-href" => "producer.path" } + %i{ng: {class: "producer.producer_icon_font"}} + %span.margin-top + %strong {{ producer.name }} + %span.producer-name{"bo-if" => "!producer.is_distributor" } + %i{ng: {class: "producer.producer_icon_font"}} + %span.margin-top + %strong {{ producer.name }} + + .columns.small-6.medium-3.large-3 %span.margin-top {{ producer.address.city }} .columns.small-4.medium-3.large-4 diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index 76dc60a7e6..0e46795701 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -1,22 +1,10 @@ = inject_enterprises -.producers.pad-top{"ng-controller" => "ProducersCtrl"} +.producers.pad-top{"ng-controller" => "EnterprisesCtrl"} .row .small-12.columns.pad-top %h1 Find local producers - / %div - / Find a - / %ofn-modal{title: "producer"} - / = render partial: "modals/producers" - / from the list below: - - #active-table-search.row - .small-12.columns - %input.animate-show{type: :text, - "ng-model" => "query", - placeholder: "Search by producer or suburb...", - "ng-debounce" => "150", - "ofn-disable-enter" => true} + = render partial: "shared/components/enterprise_search" = render partial: "producers/filters" .row{bindonce: true} @@ -24,7 +12,7 @@ .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", "scroll-after-load" => true, - "ng-repeat" => "producer in producers = (Producers.visible | filterProducers:query | taxons:activeTaxons)", + "ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons)", "ng-controller" => "ProducerNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} @@ -33,10 +21,6 @@ = render partial: 'producers/skinny' = render partial: 'producers/fat' - %producer.row{"ng-show" => "producers.length == 0"} - %p.no-results - Sorry, no results found for - %strong {{query}}. - Try another search? + = render partial: 'shared/components/enterprise_no_results' = render partial: "shared/footer" diff --git a/app/views/registration/limit_reached.html.haml b/app/views/registration/limit_reached.html.haml new file mode 100644 index 0000000000..bfaec6da3d --- /dev/null +++ b/app/views/registration/limit_reached.html.haml @@ -0,0 +1,2 @@ +/ Directive which loads the modal +%div{ "ofn-registration-limit-modal" => true } diff --git a/app/views/shared/components/_enterprise_no_results.html.haml b/app/views/shared/components/_enterprise_no_results.html.haml new file mode 100644 index 0000000000..2e6edd0875 --- /dev/null +++ b/app/views/shared/components/_enterprise_no_results.html.haml @@ -0,0 +1,5 @@ +%producer.row{"ng-show" => "filteredEnterprises.length == 0"} + %p.no-results + Sorry, no results found for + %strong {{query}}. + Try another search? diff --git a/app/views/shared/components/_enterprise_search.html.haml b/app/views/shared/components/_enterprise_search.html.haml new file mode 100644 index 0000000000..0ccb23220c --- /dev/null +++ b/app/views/shared/components/_enterprise_search.html.haml @@ -0,0 +1,7 @@ +#active-table-search.row + .small-12.columns + %input{type: :text, + "ng-model" => "query", + placeholder: "Search by name or suburb...", + "ng-debounce" => "150", + "ofn-disable-enter" => true} diff --git a/app/views/shared/components/_filter_box.html.haml b/app/views/shared/components/_filter_box.html.haml new file mode 100644 index 0000000000..0256a57ff1 --- /dev/null +++ b/app/views/shared/components/_filter_box.html.haml @@ -0,0 +1,5 @@ +.row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} + .small-12.columns + %a.button.secondary.small.expand{"ng-click" => "clearAll()"} + %i.ofn-i_009-close + Clear all filters diff --git a/app/views/shared/components/_filter_controls.html.haml b/app/views/shared/components/_filter_controls.html.haml index 4548085550..e15d2de48d 100644 --- a/app/views/shared/components/_filter_controls.html.haml +++ b/app/views/shared/components/_filter_controls.html.haml @@ -1,19 +1,9 @@ -.row - .small-12.medium-6.columns - %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", - "ng-show" => "FilterSelectorsService.selectors.length > 0"} - {{ filterText(filtersActive) }} - %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} - %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} +.small-12.medium-6.columns + %a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive", + "ng-show" => "FilterSelectorsService.selectors.length > 0"} + {{ filterText(filtersActive) }} + %i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"} + %i.ofn-i_006-caret-up{"ng-show" => "filtersActive"} - %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} - No filters - .small-12.medium-6.columns.text-right - .profile-checkbox - - / Hide until we're ready to work on this: - - / %input{type: "checkbox", name: "profile"}>< - / %label Show profiles - / %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but they may have their own physical or online shop elsewhere", "popover-placement" => "left"}>< - / %i.ofn-i_013-help + %a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"} + No filters diff --git a/app/views/shared/components/_show_profiles.html.haml b/app/views/shared/components/_show_profiles.html.haml new file mode 100644 index 0000000000..638cc47c27 --- /dev/null +++ b/app/views/shared/components/_show_profiles.html.haml @@ -0,0 +1,7 @@ +.small-12.medium-6.columns.text-right + .profile-checkbox + %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere", "popover-placement" => "left"}>< + %i.ofn-i_013-help + %label + %input{"ng-model" => "show_profiles", type: "checkbox", name: "profile"} + Show profiles diff --git a/app/views/shared/menu/_cart.html.haml b/app/views/shared/menu/_cart.html.haml index a61f80bf88..345365735b 100644 --- a/app/views/shared/menu/_cart.html.haml +++ b/app/views/shared/menu/_cart.html.haml @@ -22,20 +22,20 @@ %small {{line_item.quantity}} %i.ofn-i_009-close - {{ line_item.variant.price | currency }} + {{ line_item.variant.price_with_fees | localizeCurrency }} .columns.small-2 %small \= %strong - .right {{ line_item.variant.getPrice() | currency }} + .right {{ line_item.variant.totalPrice() | localizeCurrency }} %li.total-cart{"ng-show" => "Cart.line_items_present().length > 0"} .row .columns.small-6 %em Total: .columns.small-6.text-right - %strong {{ Cart.total() | currency }} + %strong {{ Cart.total() | localizeCurrency }} .text-right %a.button.primary.small{href: checkout_path, "ng-disabled" => "Cart.dirty"} Quick checkout diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index c57f927e61..c135274fad 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,18 +1,17 @@ -= render partial: 'shared/components/filter_controls' +.row + = render partial: 'shared/components/filter_controls' + .small-12.medium-6.columns.text-right +   .row.animate-show{"ng-show" => "filtersActive"} .small-12.columns .row.filter-box .small-12.columns - %h5.tdhead + %h5.tdhead .light Filter by Type %ul.small-block-grid-2.medium-block-grid-3.large-block-grid-4 - %taxon-selector{objects: "Products.products | products:query", + %taxon-selector{objects: "Products.products | products:query | products:showProfiles", results: "activeTaxons"} - .row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"} - .small-12.columns - %a.button.secondary.small.expand{"ng-click" => "clearAll()"} - %i.ofn-i_009-close - Clear all filters += render partial: 'shared/components/filter_box' diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 25c71f276a..82d5f7c297 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -10,7 +10,7 @@ %em from %span - %producer-modal + %enterprise-modal %i.ofn-i_036-producers {{ enterprise.name }} diff --git a/app/views/shopping_shared/_producers.html.haml b/app/views/shopping_shared/_producers.html.haml index d6ff09be4c..25c0c0599d 100644 --- a/app/views/shopping_shared/_producers.html.haml +++ b/app/views/shopping_shared/_producers.html.haml @@ -4,5 +4,6 @@ %h5 {{CurrentHub.hub.name}}'s producers: %ul.small-block-grid-2.large-block-grid-4 %li{"ng-repeat" => "enterprise in CurrentHub.hub.producers"} - %i.ofn-i_036-producers - = render partial: "modals/producer" + %enterprise-modal + %i.ofn-i_036-producers + {{ enterprise.name }} diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml index 7a78a191b6..c19ec56755 100644 --- a/app/views/shopping_shared/_tabs.html.haml +++ b/app/views/shopping_shared/_tabs.html.haml @@ -1,14 +1,14 @@ #tabs{"ng-controller" => "TabsCtrl"} .row %tabset.small-12.columns - // WILL can we add some logic here to make the distributor name not appear at small sizes? e.g. add a class?) + -# Build all tabs. - for name, heading in { about: "About #{current_distributor.name}", producers: "Producers", groups: "Groups", contact: "Contact"} - + -# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl. %tab{heading: heading, id: "tab_#{name}", - active: "active(#{name}.path)", - select: "select(#{name})"} + active: "active(\'#{name}\')", + select: "select(\'#{name}\')"} = render "shopping_shared/#{name}" diff --git a/app/views/spree/admin/overview/_unconfirmed.html.haml b/app/views/spree/admin/overview/_unconfirmed.html.haml new file mode 100644 index 0000000000..05c4df2a0d --- /dev/null +++ b/app/views/spree/admin/overview/_unconfirmed.html.haml @@ -0,0 +1,4 @@ +- @enterprises.unconfirmed.each do |enterprise| + .alert + %h6= "Action Required: Please confirm the email address for #{enterprise.name}." + %span.message= "We've sent a confirmation email to #{enterprise.email}, so please check there for further instructions. Thanks!" \ No newline at end of file diff --git a/app/views/spree/admin/overview/index.html.haml b/app/views/spree/admin/overview/index.html.haml index a46a31b404..d83a71bc40 100644 --- a/app/views/spree/admin/overview/index.html.haml +++ b/app/views/spree/admin/overview/index.html.haml @@ -1,10 +1,17 @@ %h1{ :style => 'margin-bottom: 30px'} Dashboard +- if @enterprises.unconfirmed.any? + + = render partial: "spree/admin/overview/unconfirmed" + + %hr + - if @enterprises.empty? = render partial: "spree/admin/overview/enterprises" - else + - if can? :admin, Spree::Product = render partial: "spree/admin/overview/products" diff --git a/app/views/spree/admin/shared/_hubs_sidebar.html.haml b/app/views/spree/admin/shared/_hubs_sidebar.html.haml index cdd367065a..23c536402b 100644 --- a/app/views/spree/admin/shared/_hubs_sidebar.html.haml +++ b/app/views/spree/admin/shared/_hubs_sidebar.html.haml @@ -8,10 +8,13 @@ = hidden_field klass, :distributor_ids, :multiple => true, value: nil - @hubs.each do |hub| %span.four.columns.alpha.list-item{ class: "#{cycle('odd','even')}" } - %a.three.columns.alpha{ href: "#{main_app.edit_admin_enterprise_path(hub)}" } - = hub.name - %span.one.column.omega - = check_box klass, :distributor_ids, { multiple: true }, hub.id, nil + %span.four.columns + %span.three.columns.alpha + %label + = check_box klass, :distributor_ids, { multiple: true }, hub.id, nil + = hub.name + %a.one.column.omega{ href: "#{main_app.edit_admin_enterprise_path(hub)}" } + %span.icon-arrow-right - else .four.columns.alpha.list-item %span.three.columns.alpha None Available @@ -19,4 +22,4 @@ %span.icon-remove-sign %a.four.columns.alpha.button{ href: "#{main_app.admin_enterprises_path}", class: "#{hubs_color}" } MANAGE - %span.icon-arrow-right \ No newline at end of file + %span.icon-arrow-right diff --git a/app/views/spree/order_mailer/confirm_email.text.erb b/app/views/spree/order_mailer/confirm_email.text.erb deleted file mode 100644 index 932eaebe4c..0000000000 --- a/app/views/spree/order_mailer/confirm_email.text.erb +++ /dev/null @@ -1,68 +0,0 @@ -Dear <%= @order.bill_address.firstname %>, - -Please review and retain the following order information for your records. - -============================================================ -Order Summary -============================================================ -Order for: <%= @order.bill_address.full_name %> -<% @order.line_items.each do |item| %> - <%= item.variant.sku %> <%= raw(item.variant.product.supplier.name) %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (QTY: <%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %> -<% end %> -============================================================ -Subtotal: <%= number_to_currency checkout_cart_total_with_adjustments(@order) %> -<% checkout_adjustments_for_summary(@order, exclude: [:distribution]).each do |adjustment| %> - <%= raw(adjustment.label) %> <%= adjustment.display_amount %> -<% end %> -Order Total: <%= @order.display_total %> - -<% if @order.payments.first.andand.payment_method.andand.name.andand.include? "EFT" %> -============================================================ -Payment Details -============================================================ -<%= @order.payments.first.andand.payment_method.andand.description.andand.html_safe %> - -<% end %> -<% if @order.shipping_method.andand.require_ship_address %> -============================================================ -Delivery Details -============================================================ -Your order will be delivered to: -<%= @order.ship_address.to_s %> - -<% if @order.order_cycle.andand.pickup_time_for(@order.distributor) %> -Delivery on: <%= @order.order_cycle.pickup_time_for(@order.distributor) %> - -<% end %> -<% if @order.order_cycle.andand.pickup_instructions_for(@order.distributor) %> -Other delivery information: <%= @order.order_cycle.pickup_instructions_for(@order.distributor) %> - -<% end %> -<% else %> -============================================================ -Collection Details -============================================================ -<% if @order.shipping_method.andand.description %> -<%= @order.shipping_method.description.html_safe %> - -<% end %> -<% if @order.order_cycle.andand.pickup_time_for(@order.distributor) %> -Ready for collection: <%= @order.order_cycle.pickup_time_for(@order.distributor) %> - -<% end %> -<% if @order.order_cycle.andand.pickup_instructions_for(@order.distributor) %> -Collection instructions: <%= @order.order_cycle.pickup_instructions_for(@order.distributor) %> - -<% end %> -<% end %> -<% if @order.special_instructions.present? %>Order notes: <%= @order.special_instructions %> - -<% end %> - -Thanks for your support. - -<%= @order.distributor.contact %>, -<%= @order.distributor.name %> -<%= @order.distributor.phone %> -<%= @order.distributor.email %> -<%= @order.distributor.website %> diff --git a/app/views/spree/order_mailer/confirm_email.text.haml b/app/views/spree/order_mailer/confirm_email.text.haml new file mode 100644 index 0000000000..c9ad50fb63 --- /dev/null +++ b/app/views/spree/order_mailer/confirm_email.text.haml @@ -0,0 +1,63 @@ +Dear #{@order.bill_address.firstname}, + +Please review and retain the following order information for your records. +\ +============================================================ +Order Summary +============================================================ +Order for: #{@order.bill_address.full_name} +- @order.line_items.each do |item| + #{item.variant.sku} #{raw(item.variant.product.supplier.name)} #{raw(item.variant.product.name)} #{raw(item.variant.options_text)} (QTY: #{item.quantity}) @ #{item.single_money} = #{item.display_amount} +============================================================ +Subtotal: #{number_to_currency checkout_cart_total_with_adjustments(@order)} +- checkout_adjustments_for_summary(@order, exclude: [:distribution]).each do |adjustment| + #{raw(adjustment.label)} #{adjustment.display_amount} +Order Total: #{@order.display_total} +- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check" and @order.payments.first.andand.payment_method.andand.description + \ + ============================================================ + Payment Details + ============================================================ + #{@order.payments.first.andand.payment_method.andand.description.andand.html_safe} + +- if @order.shipping_method.andand.require_ship_address + \ + ============================================================ + Delivery Details + ============================================================ + Your order will be delivered to: + #{@order.ship_address.to_s} + + - if @order.shipping_method.andand.description + #{@order.shipping_method.description.html_safe} + + - if @order.order_cycle.andand.pickup_time_for(@order.distributor) + Delivery on: #{@order.order_cycle.pickup_time_for(@order.distributor)} + + - if @order.order_cycle.andand.pickup_instructions_for(@order.distributor) + Other delivery information: #{@order.order_cycle.pickup_instructions_for(@order.distributor)} + +- else + \ + ============================================================ + Collection Details + ============================================================ + - if @order.shipping_method.andand.description + #{@order.shipping_method.description.html_safe} + + - if @order.order_cycle.andand.pickup_time_for(@order.distributor) + Ready for collection: #{@order.order_cycle.pickup_time_for(@order.distributor)} + + - if @order.order_cycle.andand.pickup_instructions_for(@order.distributor) + Collection instructions: #{@order.order_cycle.pickup_instructions_for(@order.distributor)} + + - if @order.special_instructions.present? + notes: #{@order.special_instructions} +\ +Thanks for your support. + +#{@order.distributor.contact}, += @order.distributor.name += @order.distributor.phone += @order.distributor.email += @order.distributor.website diff --git a/app/views/spree/orders/_form.html.haml b/app/views/spree/orders/_form.html.haml index 214823d4cf..953ea83e18 100644 --- a/app/views/spree/orders/_form.html.haml +++ b/app/views/spree/orders/_form.html.haml @@ -24,7 +24,7 @@ %td Product \: - %span.order-total.item-total= number_to_currency @order.item_total + %span.order-total.item-total= spree_number_to_currency(@order.item_total) %td Distribution \: diff --git a/app/views/spree/shared/_products.html.haml b/app/views/spree/shared/_products.html.haml index bdb8b87600..8ea2743642 100644 --- a/app/views/spree/shared/_products.html.haml +++ b/app/views/spree/shared/_products.html.haml @@ -15,7 +15,7 @@ .product-image = link_to small_image(product, :itemprop => "image"), product, :itemprop => 'url' = link_to truncate(product.name, :length => 50), product, :class => 'info', :itemprop => "name", :title => product.name - %span.price.selling{:itemprop => "price"}= number_to_currency product.price + %span.price.selling{:itemprop => "price"}= spree_number_to_currency(product.price) - if paginated_products.respond_to?(:num_pages) - params.delete(:search) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000000..9fef52fc8c --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,5 @@ +Devise.setup do |config| + # Add a default scope to devise, to prevent it from checking + # whether other devise enabled models are signed into a session or not + config.default_scope = :spree_user +end \ No newline at end of file diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index 8078dac761..dca89955b7 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -28,6 +28,11 @@ Spree.config do |config| #config.override_actionmailer_config = false end +# TODO Work out why this is necessary +# Seems like classes within OFN module become 'uninitialized' when server reloads +# unless the empty module is explicity 'registered' here. Something to do with autoloading? +module OpenFoodNetwork +end # Add calculators category for enterprise fees module Spree @@ -43,7 +48,7 @@ module Spree end # Forcing spree to always allow SSL connections -# Since we are using config.force_ssl = true +# Since we are using config.force_ssl = true # Without this we get a redirect loop: see https://groups.google.com/forum/#!topic/spree-user/NwpqGxJ4klk SslRequirement.module_eval do protected diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index eadaf984ae..f87aa3d48a 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -27,9 +27,16 @@ module.exports = function(config) { 'app/assets/javascripts/admin/util.js.erb' ], + preprocessors: { + '**/*.coffee': ['coffee'] + }, + coffeePreprocessor: { options: { sourceMap: true + }, + transformPath: function(path) { + return path.replace(/\.coffee$/, '.js'); } }, diff --git a/config/routes.rb b/config/routes.rb index f95b0c07df..ca4a6b59ff 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,7 +6,6 @@ Openfoodnetwork::Application.routes.draw do get "/map", to: "map#index", as: :map get "/register", to: "registration#index", as: :registration - get "/register/store", to: "registration#store", as: :store_registration get "/register/auth", to: "registration#authenticate", as: :registration_auth resource :shop, controller: "shop" do @@ -35,6 +34,8 @@ Openfoodnetwork::Application.routes.draw do end end + devise_for :enterprise + namespace :admin do resources :order_cycles do post :bulk_update, on: :collection, as: :bulk_update @@ -67,6 +68,7 @@ Openfoodnetwork::Application.routes.draw do namespace :api do resources :enterprises do + post :update_image, on: :member get :managed, on: :collection get :accessible, on: :collection end diff --git a/db/migrate/20140927005000_add_dummy_for_missing_emails.rb b/db/migrate/20140927005000_add_dummy_for_missing_emails.rb new file mode 100644 index 0000000000..08f638e2a3 --- /dev/null +++ b/db/migrate/20140927005000_add_dummy_for_missing_emails.rb @@ -0,0 +1,7 @@ +class AddDummyForMissingEmails < ActiveRecord::Migration + def up + Enterprise.all.each do |enterprise| + enterprise.update_column(:email, "missing@example.com") if enterprise.read_attribute(:email).blank? + end + end +end diff --git a/db/migrate/20140927005043_enterprise_config_refactor.rb b/db/migrate/20140927005043_enterprise_config_refactor.rb new file mode 100644 index 0000000000..6b51ac6e50 --- /dev/null +++ b/db/migrate/20140927005043_enterprise_config_refactor.rb @@ -0,0 +1,57 @@ +class EnterpriseConfigRefactor < ActiveRecord::Migration + class Enterprise < ActiveRecord::Base + self.inheritance_column = nil + end + + def up + add_column :enterprises, :sells, :string, null: false, default: 'none' + add_index :enterprises, :sells + add_index :enterprises, [:is_primary_producer, :sells] + + Enterprise.reset_column_information + + Enterprise.all.each do |enterprise| + enterprise.update_attributes!({:sells => sells_what?(enterprise)}) + end + + remove_column :enterprises, :type + remove_column :enterprises, :is_distributor + end + + def down + # This process is lossy. Producer profiles wont exist. + add_column :enterprises, :type, :string, null: false, default: 'profile' + add_column :enterprises, :is_distributor, :boolean + + Enterprise.reset_column_information + + Enterprise.all.each do |enterprise| + enterprise.update_attributes!({ + :type => type?(enterprise), + :is_distributor => distributes?(enterprise) + }) + end + + remove_column :enterprises, :sells + end + + def sells_what?(enterprise) + is_distributor = enterprise.read_attribute(:is_distributor) + is_primary_producer = enterprise.read_attribute(:is_primary_producer) + type = enterprise.read_attribute(:type) + return "own" if type == "single" && (is_distributor || is_primary_producer) + return "none" if !is_distributor || type == "profile" + return "any" + end + + def distributes?(enterprise) + enterprise.read_attribute(:sells) != "none" + end + + def type?(enterprise) + sells = enterprise.read_attribute(:sells) + return "profile" if sells == "none" + return "single" if sells == "own" + return "full" + end +end diff --git a/db/migrate/20141010043405_add_confirmable_to_enterprise.rb b/db/migrate/20141010043405_add_confirmable_to_enterprise.rb new file mode 100644 index 0000000000..22203fe419 --- /dev/null +++ b/db/migrate/20141010043405_add_confirmable_to_enterprise.rb @@ -0,0 +1,16 @@ +class AddConfirmableToEnterprise < ActiveRecord::Migration + def up + add_column :enterprises, :confirmation_token, :string + add_column :enterprises, :confirmed_at, :datetime + add_column :enterprises, :confirmation_sent_at, :datetime + add_column :enterprises, :unconfirmed_email, :string + add_index :enterprises, :confirmation_token, :unique => true + + # Existing enterprises are assumed to be confirmed + Enterprise.update_all(:confirmed_at => Time.now) + end + + def down + remove_columns :enterprises, :confirmation_token, :confirmed_at, :confirmation_sent_at, :unconfirmed_email + end +end diff --git a/db/schema.rb b/db/schema.rb index c8005a42e9..974dc8ac9b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140904003026) do +ActiveRecord::Schema.define(:version => 20141010043405) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -238,7 +238,6 @@ ActiveRecord::Schema.define(:version => 20140904003026) do t.string "description" t.text "long_description" t.boolean "is_primary_producer" - t.boolean "is_distributor" t.string "contact" t.string "phone" t.string "email" @@ -249,8 +248,8 @@ ActiveRecord::Schema.define(:version => 20140904003026) do t.integer "address_id" t.string "pickup_times" t.string "next_collection_at" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.text "distributor_info" t.string "logo_file_name" t.string "logo_content_type" @@ -264,12 +263,19 @@ ActiveRecord::Schema.define(:version => 20140904003026) do t.string "facebook" t.string "instagram" t.string "linkedin" - t.string "type", :default => "profile", :null => false - t.integer "owner_id", :null => false + t.integer "owner_id", :null => false + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.string "sells", :default => "none", :null => false end add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id" + add_index "enterprises", ["confirmation_token"], :name => "index_enterprises_on_confirmation_token", :unique => true + add_index "enterprises", ["is_primary_producer", "sells"], :name => "index_enterprises_on_is_primary_producer_and_sells" add_index "enterprises", ["owner_id"], :name => "index_enterprises_on_owner_id" + add_index "enterprises", ["sells"], :name => "index_enterprises_on_sells" create_table "exchange_fees", :force => true do |t| t.integer "exchange_id" @@ -565,9 +571,9 @@ ActiveRecord::Schema.define(:version => 20140904003026) 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" end diff --git a/lib/open_food_network/spree_api_key_loader.rb b/lib/open_food_network/spree_api_key_loader.rb index 36fa4b9961..5eb7236221 100644 --- a/lib/open_food_network/spree_api_key_loader.rb +++ b/lib/open_food_network/spree_api_key_loader.rb @@ -9,4 +9,4 @@ module OpenFoodNetwork end end end -end \ No newline at end of file +end diff --git a/script/backup.sh b/script/backup.sh index 462aef6abe..114a8352c6 100755 --- a/script/backup.sh +++ b/script/backup.sh @@ -4,4 +4,5 @@ set -e +mkdir -p db/backup ssh $1 "pg_dump -h localhost -U openfoodweb openfoodweb_production |gzip" > db/backup/$1-`date +%Y%m%d`.sql.gz diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index 44421aad8e..4120f163e2 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' module Admin describe EnterprisesController do + include AuthenticationWorkflow let(:distributor_owner) do user = create(:user) user.spree_roles = [] @@ -23,7 +24,7 @@ module Admin describe "creating an enterprise" do let(:country) { Spree::Country.find_by_name 'Australia' } let(:state) { Spree::State.find_by_name 'Victoria' } - let(:enterprise_params) { {enterprise: {name: 'zzz', address_attributes: {address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id}}} } + let(:enterprise_params) { {enterprise: {name: 'zzz', email: "bob@example.com", address_attributes: {address1: 'a', city: 'a', zipcode: 'a', country_id: country.id, state_id: state.id}}} } it "grants management permission if the current user is an enterprise user" do controller.stub spree_current_user: user @@ -83,63 +84,79 @@ module Admin end describe "updating an enterprise" do - let(:profile_enterprise) { create(:enterprise, type: 'profile') } + let(:profile_enterprise) { create(:enterprise, sells: 'none') } context "as manager" do - it "does not allow 'type' to be changed" do + it "does not allow 'sells' to be changed" do profile_enterprise.enterprise_roles.build(user: user).save controller.stub spree_current_user: user - enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } + enterprise_params = { id: profile_enterprise.id, enterprise: { sells: 'any' } } spree_put :update, enterprise_params profile_enterprise.reload - expect(profile_enterprise.type).to eq 'profile' + expect(profile_enterprise.sells).to eq 'none' end end context "as super admin" do - it "allows 'type' to be changed" do + it "allows 'sells' to be changed" do controller.stub spree_current_user: admin_user - enterprise_params = { id: profile_enterprise.id, enterprise: { type: 'full' } } + enterprise_params = { id: profile_enterprise.id, enterprise: { sells: 'any' } } spree_put :update, enterprise_params profile_enterprise.reload - expect(profile_enterprise.type).to eq 'full' + expect(profile_enterprise.sells).to eq 'any' end end end describe "bulk updating enterprises" do - let(:profile_enterprise1) { create(:enterprise, type: 'profile') } - let(:profile_enterprise2) { create(:enterprise, type: 'profile') } + let!(:original_owner) do + user = create_enterprise_user + user.enterprise_limit = 2 + user.save! + user + end + let!(:new_owner) do + user = create_enterprise_user + user.enterprise_limit = 2 + user.save! + user + end + let!(:profile_enterprise1) { create(:enterprise, sells: 'none', owner: original_owner ) } + let!(:profile_enterprise2) { create(:enterprise, sells: 'none', owner: original_owner ) } context "as manager" do - it "does not allow 'type' to be changed" do - profile_enterprise1.enterprise_roles.build(user: user).save - profile_enterprise2.enterprise_roles.build(user: user).save - controller.stub spree_current_user: user - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, type: 'full' }, '1' => { id: profile_enterprise2.id, type: 'full' } } } } + it "does not allow 'sells' or 'owner' to be changed" do + profile_enterprise1.enterprise_roles.build(user: new_owner).save + profile_enterprise2.enterprise_roles.build(user: new_owner).save + controller.stub spree_current_user: new_owner + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } spree_put :bulk_update, bulk_enterprise_params profile_enterprise1.reload profile_enterprise2.reload - expect(profile_enterprise1.type).to eq 'profile' - expect(profile_enterprise2.type).to eq 'profile' + expect(profile_enterprise1.sells).to eq 'none' + expect(profile_enterprise2.sells).to eq 'none' + expect(profile_enterprise1.owner).to eq original_owner + expect(profile_enterprise2.owner).to eq original_owner end end context "as super admin" do - it "allows 'type' to be changed" do - profile_enterprise1.enterprise_roles.build(user: user).save - profile_enterprise2.enterprise_roles.build(user: user).save + it "allows 'sells' and 'owner' to be changed" do + profile_enterprise1.enterprise_roles.build(user: new_owner).save + profile_enterprise2.enterprise_roles.build(user: new_owner).save controller.stub spree_current_user: admin_user - bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, type: 'full' }, '1' => { id: profile_enterprise2.id, type: 'full' } } } } + bulk_enterprise_params = { enterprise_set: { collection_attributes: { '0' => { id: profile_enterprise1.id, sells: 'any', owner_id: new_owner.id }, '1' => { id: profile_enterprise2.id, sells: 'any', owner_id: new_owner.id } } } } spree_put :bulk_update, bulk_enterprise_params profile_enterprise1.reload profile_enterprise2.reload - expect(profile_enterprise1.type).to eq 'full' - expect(profile_enterprise2.type).to eq 'full' + expect(profile_enterprise1.sells).to eq 'any' + expect(profile_enterprise2.sells).to eq 'any' + expect(profile_enterprise1.owner).to eq new_owner + expect(profile_enterprise2.owner).to eq new_owner end end end diff --git a/spec/controllers/api/enterprises_controller_spec.rb b/spec/controllers/api/enterprises_controller_spec.rb new file mode 100644 index 0000000000..e21a879d4f --- /dev/null +++ b/spec/controllers/api/enterprises_controller_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +module Api + describe EnterprisesController do + include AuthenticationWorkflow + render_views + + let(:enterprise) { create(:distributor_enterprise) } + + before do + stub_authentication! + Enterprise.stub(:find).and_return(enterprise) + end + + describe "as an enterprise manager" do + let(:enterprise_manager) { create_enterprise_user } + + before do + enterprise_manager.enterprise_roles.build(enterprise: enterprise).save + Spree.user_class.stub :find_by_spree_api_key => enterprise_manager + end + + describe "submitting a valid image" do + before do + enterprise.stub(:update_attributes).and_return(true) + end + + it "I can update enterprise image" do + spree_post :update_image, logo: 'a logo' + response.should be_success + end + end + end + + describe "as an non-managing user" do + let(:non_managing_user) { create_enterprise_user } + + before do + Spree.user_class.stub :find_by_spree_api_key => non_managing_user + end + + describe "submitting a valid image" do + before do + enterprise.stub(:update_attributes).and_return(true) + end + + it "I can't update enterprise image" do + spree_post :update_image, logo: 'a logo' + assert_unauthorized! + end + end + end + end +end diff --git a/spec/controllers/devise/confirmation_controller_spec.rb b/spec/controllers/devise/confirmation_controller_spec.rb new file mode 100644 index 0000000000..b8719bbd8f --- /dev/null +++ b/spec/controllers/devise/confirmation_controller_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Devise::ConfirmationsController do + context "after confirmation" do + before do + e = create(:enterprise, confirmed_at: nil) + @request.env["devise.mapping"] = Devise.mappings[:enterprise] + spree_get :show, confirmation_token: e.confirmation_token + end + + it "should redirect to admin root" do + expect(response).to redirect_to spree.admin_path + end + end +end \ No newline at end of file diff --git a/spec/controllers/registration_controller_spec.rb b/spec/controllers/registration_controller_spec.rb index 49efc005f6..8ef292dc68 100644 --- a/spec/controllers/registration_controller_spec.rb +++ b/spec/controllers/registration_controller_spec.rb @@ -1,15 +1,40 @@ require 'spec_helper' describe RegistrationController do + include AuthenticationWorkflow describe "redirecting when user not logged in" do it "index" do get :index response.should redirect_to registration_auth_path(anchor: "signup?after_login=/register") end + end - it "store" do - get :store - response.should redirect_to registration_auth_path(anchor: "signup?after_login=/register/store") + describe "redirecting when user has reached enterprise ownership limit" do + let!(:user) { create_enterprise_user( enterprise_limit: 1 ) } + let!(:enterprise) { create(:distributor_enterprise, owner: user) } + + before do + controller.stub spree_current_user: user + end + + it "index" do + get :index + response.should render_template :limit_reached + end + end + + describe "loading data when user is logged in" do + let!(:user) { create_enterprise_user } + + before do + controller.stub spree_current_user: user + end + + describe "index" do + it "loads the spree api key" do + get :index + expect(assigns(:spree_api_key)).to eq user.spree_api_key + end end end end diff --git a/spec/controllers/spree/admin/payment_methods_controller_spec.rb b/spec/controllers/spree/admin/payment_methods_controller_spec.rb index f3266f69c2..c27fe16f19 100644 --- a/spec/controllers/spree/admin/payment_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/payment_methods_controller_spec.rb @@ -73,4 +73,4 @@ describe Spree::Admin::PaymentMethodsController do end end end -end \ No newline at end of file +end diff --git a/spec/factories.rb b/spec/factories.rb index d605b5b759..60b94b08c0 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -10,21 +10,29 @@ FactoryGirl.define do after(:create) do |oc| # Suppliers + supplier1 = create(:supplier_enterprise) + supplier2 = create(:supplier_enterprise) + + # Incoming Exchanges ex1 = create(:exchange, :order_cycle => oc, :incoming => true, - :sender => create(:supplier_enterprise), :receiver => oc.coordinator) + :sender => supplier1, :receiver => oc.coordinator) ex2 = create(:exchange, :order_cycle => oc, :incoming => true, - :sender => create(:supplier_enterprise), :receiver => oc.coordinator) + :sender => supplier2, :receiver => oc.coordinator) ExchangeFee.create!(exchange: ex1, enterprise_fee: create(:enterprise_fee, enterprise: ex1.sender)) ExchangeFee.create!(exchange: ex2, enterprise_fee: create(:enterprise_fee, enterprise: ex2.sender)) - # Distributors + #Distributors + distributor1 = create(:distributor_enterprise) + distributor2 = create(:distributor_enterprise) + + # Outgoing Exchanges ex3 = create(:exchange, :order_cycle => oc, :incoming => false, - :sender => oc.coordinator, :receiver => create(:distributor_enterprise), + :sender => oc.coordinator, :receiver => distributor1, :pickup_time => 'time 0', :pickup_instructions => 'instructions 0') ex4 = create(:exchange, :order_cycle => oc, :incoming => false, - :sender => oc.coordinator, :receiver => create(:distributor_enterprise), + :sender => oc.coordinator, :receiver => distributor2, :pickup_time => 'time 1', :pickup_instructions => 'instructions 1') ExchangeFee.create!(exchange: ex3, enterprise_fee: create(:enterprise_fee, enterprise: ex3.receiver)) @@ -84,21 +92,22 @@ FactoryGirl.define do factory :enterprise, :class => Enterprise do owner { FactoryGirl.create :user } sequence(:name) { |n| "Enterprise #{n}" } - type 'full' + sells 'any' description 'enterprise' long_description '

Hello, world!

This is a paragraph.

' email 'enterprise@example.com' address { FactoryGirl.create(:address) } + confirmed_at { Time.now } end factory :supplier_enterprise, :parent => :enterprise do is_primary_producer true - is_distributor false + sells "none" end factory :distributor_enterprise, :parent => :enterprise do is_primary_producer false - is_distributor true + sells "any" end factory :enterprise_relationship do diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index d47c699c8f..8bb54cfca8 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -6,7 +6,7 @@ feature %q{ } , js: true do include AuthenticationWorkflow include WebHelper - + describe "listing products" do before :each do login_to_admin_section @@ -69,7 +69,7 @@ feature %q{ expect(page).to have_field "price", with: "44.0" expect(page).to have_no_field "price", with: "66.0", visible: true end - + it "displays an on hand count input for each product (ie. for master variant) if no regular variants exist" do p1 = FactoryGirl.create(:product) p2 = FactoryGirl.create(:product) @@ -84,7 +84,7 @@ feature %q{ expect(page).to have_field "on_hand", with: "15" expect(page).to have_field "on_hand", with: "12" end - + it "displays an on hand count in a span for each product (ie. for master variant) if other variants exist" do p1 = FactoryGirl.create(:product) p2 = FactoryGirl.create(:product) @@ -142,7 +142,7 @@ feature %q{ expect(page).to have_field "variant_unit_name", with: "packet" end end - + describe "listing variants" do before :each do login_to_admin_section @@ -174,8 +174,8 @@ feature %q{ expect(page).to have_field "variant_on_hand", with: "15" expect(page).to have_field "variant_on_hand", with: "6" end - - + + it "displays a price input (for each variant) for each product" do p1 = FactoryGirl.create(:product, price: 2.0) v1 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 12.75) @@ -295,7 +295,7 @@ feature %q{ login_to_admin_section 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 @@ -311,7 +311,7 @@ feature %q{ fill_in "product_name", with: "Big Bag Of Potatoes" select s2.name, :from => 'producer' - fill_in "available_on", with: (Date.today-3).strftime("%F %T") + fill_in "available_on", with: (3.days.ago.beginning_of_day).strftime("%F %T") fill_in "price", with: "20" select "Weight (kg)", from: "variant_unit_with_scale" select2_select t1.name, from: "p#{p.id}_category" @@ -333,7 +333,7 @@ feature %q{ expect(p.on_hand).to eq 18 expect(p.primary_taxon).to eq t1 end - + scenario "updating a product with a variant unit of 'items'" do p = FactoryGirl.create(:product, variant_unit: 'weight', variant_unit_scale: 1000) @@ -589,7 +589,7 @@ feature %q{ within "tr#v_#{v1.id}" do first("a.delete-variant").click end - + expect(page).to have_selector "a.delete-variant", :count => 2 visit '/admin/products/bulk_edit' @@ -672,7 +672,7 @@ feature %q{ login_to_admin_section 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 @@ -814,7 +814,7 @@ feature %q{ fill_in "product_name", with: "Big Bag Of Potatoes" select(supplier_managed2.name, :from => 'producer') - fill_in "available_on", with: (Date.today-3).strftime("%F %T") + fill_in "available_on", with: (3.days.ago.beginning_of_day).strftime("%F %T") fill_in "price", with: "20" select "Weight (kg)", from: "variant_unit_with_scale" fill_in "on_hand", with: "18" diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index bfc624bce5..5e4366b6f7 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -89,7 +89,7 @@ feature %q{ let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } let!(:d3) { create(:distributor_enterprise) } - let(:enterprise_user) { create_enterprise_user([d1]) } + let(:enterprise_user) { create_enterprise_user( enterprises: [d1] ) } let!(:er1) { create(:enterprise_relationship, parent: d1, child: d2) } let!(:er2) { create(:enterprise_relationship, parent: d2, child: d1) } diff --git a/spec/features/admin/enterprise_user_spec.rb b/spec/features/admin/enterprise_user_spec.rb index 77378f6cbd..232ffb68ae 100644 --- a/spec/features/admin/enterprise_user_spec.rb +++ b/spec/features/admin/enterprise_user_spec.rb @@ -11,10 +11,10 @@ feature %q{ let!(:user) { create_enterprise_user } let!(:supplier1) { create(:supplier_enterprise, name: 'Supplier 1') } let!(:supplier2) { create(:supplier_enterprise, name: 'Supplier 2') } - let(:supplier_profile) { create(:supplier_enterprise, name: 'Supplier profile', type: 'profile') } + let(:supplier_profile) { create(:supplier_enterprise, name: 'Supplier profile', sells: 'none') } let!(:distributor1) { create(:distributor_enterprise, name: 'Distributor 3') } let!(:distributor2) { create(:distributor_enterprise, name: 'Distributor 4') } - let(:distributor_profile) { create(:distributor_enterprise, name: 'Distributor profile', type: 'profile') } + let(:distributor_profile) { create(:distributor_enterprise, name: 'Distributor profile', sells: 'none') } describe "creating an enterprise user" do context "with a limitted number of owned enterprises" do @@ -53,7 +53,10 @@ feature %q{ end end - describe "with only a profile-level enterprise" do + # This case no longer exists as anyone with an enterprise can supply into the system. + # Or can they?? There is no producer profile anyway. + # TODO discuss what parts of this are still necessary in which cases. + pending "with only a profile-level enterprise" do before do user.enterprise_roles.create! enterprise: supplier_profile user.enterprise_roles.create! enterprise: distributor_profile @@ -64,7 +67,7 @@ feature %q{ page.should have_admin_menu_item 'Dashboard' page.should have_admin_menu_item 'Enterprises' - ['Orders', 'Products', 'Reports', 'Configuration', 'Promotions', 'Users', 'Order Cycles'].each do |menu_item_name| + ['Orders', 'Reports', 'Configuration', 'Promotions', 'Users', 'Order Cycles'].each do |menu_item_name| page.should_not have_admin_menu_item menu_item_name end end @@ -79,15 +82,15 @@ feature %q{ end end - it "does not show me product management controls" do - page.should_not have_selector '#products' + it "shows me product management controls, but not order_cycle controls" do + page.should have_selector '#products' page.should_not have_selector '#order_cycles' end - it "does not show me enterprise product info, payment methods, shipping methods or enterprise fees" do + it "shows me enterprise product info but not payment methods, shipping methods or enterprise fees" do # Producer product info - page.should_not have_selector '.producers_tab span', text: 'Total Products' - page.should_not have_selector '.producers_tab span', text: 'Active Products' + page.should have_selector '.producers_tab span', text: 'Total Products' + page.should have_selector '.producers_tab span', text: 'Active Products' page.should_not have_selector '.producers_tab span', text: 'Products in OCs' # Payment methods, shipping methods, enterprise fees diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index abaab5cd2d..06b1fd31a6 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -16,7 +16,7 @@ feature %q{ within("tr.enterprise-#{s.id}") do expect(page).to have_content s.name - expect(page).to have_select "enterprise_set_collection_attributes_1_type" + expect(page).to have_select "enterprise_set_collection_attributes_1_sells" expect(page).to have_content "Edit Profile" expect(page).to have_content "Delete" expect(page).to_not have_content "Payment Methods" @@ -26,7 +26,7 @@ feature %q{ within("tr.enterprise-#{d.id}") do expect(page).to have_content d.name - expect(page).to have_select "enterprise_set_collection_attributes_0_type" + expect(page).to have_select "enterprise_set_collection_attributes_0_sells" expect(page).to have_content "Edit Profile" expect(page).to have_content "Delete" expect(page).to have_content "Payment Methods" @@ -37,7 +37,7 @@ feature %q{ scenario "editing enterprises in bulk" do s = create(:supplier_enterprise) - d = create(:distributor_enterprise, type: 'profile') + d = create(:distributor_enterprise, sells: 'none') d_manager = create_enterprise_user d_manager.enterprise_roles.build(enterprise: d).save expect(d.owner).to_not eq d_manager @@ -48,14 +48,14 @@ feature %q{ within("tr.enterprise-#{d.id}") do expect(page).to have_checked_field "enterprise_set_collection_attributes_0_visible" uncheck "enterprise_set_collection_attributes_0_visible" - select 'full', from: "enterprise_set_collection_attributes_0_type" + select 'any', from: "enterprise_set_collection_attributes_0_sells" select d_manager.email, from: 'enterprise_set_collection_attributes_0_owner_id' end click_button "Update" flash_message.should == 'Enterprises updated successfully' distributor = Enterprise.find(d.id) expect(distributor.visible).to eq false - expect(distributor.type).to eq 'full' + expect(distributor.sells).to eq 'any' expect(distributor.owner).to eq d_manager end @@ -82,15 +82,16 @@ feature %q{ click_link 'New Enterprise' # Checking shipping and payment method sidebars work + choose "Any" uncheck 'enterprise_is_primary_producer' - check 'enterprise_is_distributor' + page.should_not have_checked_field "enterprise_payment_method_ids_#{payment_method.id}" page.should_not have_checked_field "enterprise_shipping_method_ids_#{shipping_method.id}" # Filling in details fill_in 'enterprise_name', :with => 'Eaterprises' select2_search admin.email, from: 'Owner' - choose 'Full' + choose 'Any' check "enterprise_payment_method_ids_#{payment_method.id}" check "enterprise_shipping_method_ids_#{shipping_method.id}" select2_search eg1.name, from: 'Groups' @@ -109,8 +110,8 @@ feature %q{ fill_in 'enterprise_address_attributes_zipcode', :with => '3072' select2_search 'Australia', :from => 'Country' select2_search 'Victoria', :from => 'State' - fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' - fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' + long_description = find :css, "text-angular div.ta-scroll-window div.ta-bind" + long_description.set 'Connecting farmers and eaters' click_button 'Create' flash_message.should == 'Enterprise "Eaterprises" has been successfully created!' @@ -134,21 +135,31 @@ feature %q{ end fill_in 'enterprise_name', :with => 'Eaterprises' - choose 'Single' + choose 'Own' select2_search user.email, from: 'Owner' fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' - fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' + long_description = find :css, "text-angular div.ta-scroll-window div.ta-bind" + long_description.set 'This is an interesting long description' # Check Angularjs switching of sidebar elements uncheck 'enterprise_is_primary_producer' - uncheck 'enterprise_is_distributor' + choose 'None' + page.should have_selector "#enterprise_fees", visible: false page.should have_selector "#payment_methods", visible: false page.should have_selector "#shipping_methods", visible: false - page.should have_selector "#enterprise_fees", visible: false - check 'enterprise_is_distributor' + check 'enterprise_is_primary_producer' + page.should have_selector "#enterprise_fees" + page.should have_selector "#payment_methods", visible: false + page.should have_selector "#shipping_methods", visible: false + uncheck 'enterprise_is_primary_producer' + choose 'Own' + page.should have_selector "#enterprise_fees" page.should have_selector "#payment_methods" page.should have_selector "#shipping_methods" + choose 'Any' page.should have_selector "#enterprise_fees" + page.should have_selector "#payment_methods" + page.should have_selector "#shipping_methods" select2_search eg1.name, from: 'Groups' @@ -182,6 +193,7 @@ feature %q{ page.should have_checked_field "enterprise_payment_method_ids_#{payment_method.id}" page.should have_checked_field "enterprise_shipping_method_ids_#{shipping_method.id}" page.should have_selector "a.list-item", text: enterprise_fee.name + page.should have_content 'This is an interesting long description' end describe "producer properties" do @@ -275,16 +287,14 @@ feature %q{ within("tr.enterprise-#{distributor1.id}") do expect(page).to have_content distributor1.name - expect(page).to have_checked_field "enterprise_set_collection_attributes_0_is_distributor" expect(page).to have_unchecked_field "enterprise_set_collection_attributes_0_is_primary_producer" - expect(page).to_not have_select "enterprise_set_collection_attributes_0_type" + expect(page).to_not have_select "enterprise_set_collection_attributes_0_sells" end within("tr.enterprise-#{supplier1.id}") do expect(page).to have_content supplier1.name - expect(page).to have_unchecked_field "enterprise_set_collection_attributes_1_is_distributor" expect(page).to have_checked_field "enterprise_set_collection_attributes_1_is_primary_producer" - expect(page).to_not have_select "enterprise_set_collection_attributes_1_type" + expect(page).to_not have_select "enterprise_set_collection_attributes_1_sells" end expect(page).to_not have_content "supplier2.name" @@ -312,6 +322,7 @@ feature %q{ click_link 'Enterprises' click_link 'New Enterprise' fill_in 'enterprise_name', with: 'zzz' + fill_in 'enterprise_email', with: 'bob@example.com' fill_in 'enterprise_address_attributes_address1', with: 'z' fill_in 'enterprise_address_attributes_city', with: 'z' fill_in 'enterprise_address_attributes_zipcode', with: 'z' diff --git a/spec/features/admin/image_settings_spec.rb b/spec/features/admin/image_settings_spec.rb new file mode 100644 index 0000000000..a83a072235 --- /dev/null +++ b/spec/features/admin/image_settings_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +feature %q{ + As an admin + I want to manage image formats +} do + include AuthenticationWorkflow + include WebHelper + + before(:all) do + styles = {"mini" => "48x48>", + "small" => "100x100>", + "product" => "240x240>", + "large" => "600x600>"} + + Spree::Config[:attachment_styles] = ActiveSupport::JSON.encode(styles) + Spree::Image.attachment_definitions[:attachment][:styles] = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]) + Spree::Image.reformat_styles + end + + scenario "setting the image format for a paperclip style" do + # When I go to the image settings page + login_to_admin_section + visit spree.edit_admin_image_settings_path + + # All the styles should default to "Unchanged" + page.should have_select 'attachment_styles_format_mini', selected: 'Unchanged' + page.should have_select 'attachment_styles_format_small', selected: 'Unchanged' + page.should have_select 'attachment_styles_format_product', selected: 'Unchanged' + page.should have_select 'attachment_styles_format_large', selected: 'Unchanged' + + # When I change a style to "PNG" and save + select 'PNG', from: 'attachment_styles_format_mini' + click_button 'Update' + + # Then the change should be saved to the image formats + page.should have_content "Image Settings successfully updated." + page.should have_select 'attachment_styles_format_mini', selected: 'PNG' + + styles = Spree::Image.attachment_definitions[:attachment][:styles] + styles[:mini].should == ['48x48>', :png] + end +end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index febd966109..be91d442e1 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -471,6 +471,10 @@ feature %q{ # I should see only the order cycle I am coordinating page.should have_content oc_user_coordinating.name page.should_not have_content oc_for_other_user.name + + # The order cycle should show enterprises that I manage + page.should have_selector 'td.suppliers', text: supplier_managed.name + page.should have_selector 'td.distributors', text: distributor_managed.name # The order cycle should not show enterprises that I don't manage page.should_not have_selector 'td.suppliers', text: supplier_unmanaged.name diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index dd4a2113e8..908ce4042e 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -110,10 +110,10 @@ feature %q{ let(:coordinator2) { create(:distributor_enterprise) } let!(:order_cycle1) { create(:order_cycle, coordinator: coordinator1) } let!(:order_cycle2) { create(:simple_order_cycle, coordinator: coordinator2) } - let(:supplier1) { order_cycle1.suppliers.first } - let(:supplier2) { order_cycle1.suppliers.last } - let(:distributor1) { order_cycle1.distributors.first } - let(:distributor2) { order_cycle1.distributors.last } + let!(:supplier1) { order_cycle1.suppliers.first } + let!(:supplier2) { order_cycle1.suppliers.last } + let!(:distributor1) { order_cycle1.distributors.first } + let!(:distributor2) { order_cycle1.distributors.reject{ |d| d == distributor1 }.last } # ensure d1 != d2 let(:product) { order_cycle1.products.first } before(:each) do @@ -131,6 +131,7 @@ feature %q{ expect(page).to have_content 'ADD PRODUCT' targetted_select2_search product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' + click_link 'Add' 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 diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index e880f25d23..5228b4c8a0 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -10,8 +10,8 @@ feature %q{ context "Permissions for different reports" do context "As an enterprise user" do let(:user) do - create_enterprise_user([ - create(:distributor_enterprise) + create_enterprise_user(enterprises: [ + create(:distributor_enterprise) ]) end it "should not show the Sales Total report" do @@ -99,7 +99,7 @@ feature %q{ let(:shipping_instructions) { "pick up on thursday please!" } let(:order1) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) } let(:order2) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) } - + before do Timecop.travel(Time.zone.local(2013, 4, 25, 14, 0, 0)) { order1.finalize! } Timecop.travel(Time.zone.local(2013, 4, 25, 16, 0, 0)) { order2.finalize! } @@ -144,7 +144,7 @@ feature %q{ variant_2.update_column(:count_on_hand, 20) product_2.master.update_column(:count_on_hand, 9) variant_1.option_values = [create(:option_value, :presentation => "Test")] - + login_to_admin_section click_link 'Reports' @@ -165,4 +165,3 @@ feature %q{ end end end - diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index 1904f3d5af..19767d8ea2 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -1,4 +1,4 @@ -require "spec_helper" +require 'spec_helper' feature %q{ As an admin diff --git a/spec/features/consumer/producers_spec.rb b/spec/features/consumer/producers_spec.rb index 98bef7f084..6e09365195 100644 --- a/spec/features/consumer/producers_spec.rb +++ b/spec/features/consumer/producers_spec.rb @@ -3,14 +3,14 @@ require 'spec_helper' feature %q{ As a consumer I want to see a list of producers - So that I can shop at hubs distributing their products + So that I can shop at hubs distributing their products }, js: true do include UIComponentHelper let!(:producer) { create(:supplier_enterprise) } let!(:invisible_producer) { create(:supplier_enterprise, visible: false) } let(:taxon) { create(:taxon) } let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) } - + before do visit producers_path end @@ -20,7 +20,7 @@ feature %q{ expand_active_table_node producer.name page.should have_content producer.supplied_taxons.first.name.split.map(&:capitalize).join(' ') end - + it "doesn't show invisible producers" do page.should_not have_content invisible_producer.name end diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb index 04e244e205..ea09450467 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -11,36 +11,28 @@ feature "Registration", js: true do expect(URI.parse(current_url).path).to eq registration_auth_path - sleep 0.5 # TOTO: DEAL WITH ME + page.has_selector? "dd", text: "Log in" + switch_to_login_tab - # Logging in - click_link "Log in" + # Enter Login details fill_in "Email", with: user.email fill_in "Password", with: user.password - click_button 'Log in' + click_login_and_ensure_content "Hi there!" - # Log in was successful, introduction shown - sleep 0.5 # TOTO: DEAL WITH ME - - expect(page).to have_content "This wizard will step you through creating a profile" expect(URI.parse(current_url).path).to eq registration_path # Done reading introduction - click_button "Let's get started!" + click_button_and_ensure_content "Let's get started!", "Woot! First we need to know a little bit about your enterprise:" # Filling in details - expect(page).to have_content "Woot! First we need to know what sort of enterprise you are:" fill_in 'enterprise_name', with: "My Awesome Enterprise" - click_link 'both-panel' - click_button 'Continue' # Filling in address - expect(page).to have_content 'Greetings My Awesome Enterprise' fill_in 'enterprise_address', with: '123 Abc Street' fill_in 'enterprise_city', with: 'Northcote' fill_in 'enterprise_zipcode', with: '3070' select 'Australia', from: 'enterprise_country' - select 'Vic', from: 'enterprise_state' + select 'VIC', from: 'enterprise_state' click_button 'Continue' # Filling in Contact Details @@ -50,11 +42,16 @@ feature "Registration", js: true do fill_in 'enterprise_phone', with: '12 3456 7890' click_button 'Continue' + # Choosing a type + expect(page).to have_content 'Last step to add My Awesome Enterprise!' + click_link 'producer-panel' + click_button 'Continue' + # Enterprise should be created expect(page).to have_content 'Nice one!' e = Enterprise.find_by_name('My Awesome Enterprise') expect(e.address.address1).to eq "123 Abc Street" - expect(e.is_distributor).to eq true + expect(e.sells).to eq "none" expect(e.is_primary_producer).to eq true expect(e.contact).to eq "Saskia Munroe" @@ -65,15 +62,22 @@ feature "Registration", js: true do fill_in 'enterprise_acn', with: '54321' click_button 'Continue' - # Enterprise should be updated - expect(page).to have_content 'Last step!' + # Enterprise should be update + expect(page).to have_content "Let's upload some pretty pictures so your profile looks great!" e.reload expect(e.description).to eq "Short description" expect(e.long_description).to eq "Long description" expect(e.abn).to eq '12345' expect(e.acn).to eq '54321' + # Images + # Move from logo page + click_button 'Continue' + # Move from promo page + click_button 'Continue' + # Filling in social + expect(page).to have_content 'How can people find My Awesome Enterprise online?' fill_in 'enterprise_website', with: 'www.shop.com' fill_in 'enterprise_facebook', with: 'FaCeBoOk' fill_in 'enterprise_linkedin', with: 'LiNkEdIn' @@ -82,7 +86,7 @@ feature "Registration", js: true do click_button 'Continue' # Done - expect(page).to have_content "You have successfully completed the profile for My Awesome Enterprise" + expect(page).to have_content "Finished!" e.reload expect(e.website).to eq "www.shop.com" expect(e.facebook).to eq "FaCeBoOk" @@ -90,34 +94,36 @@ feature "Registration", js: true do expect(e.twitter).to eq "@TwItTeR" expect(e.instagram).to eq "@InStAgRaM" end + end - it "Allows a logged in user to register a store" do - visit store_registration_path + def switch_to_login_tab + # Link appears to be unresponsive for a while, so keep clicking it until it works + using_wait_time 0.5 do + 10.times do + click_link "Log in" + break if page.has_selector? "dd.active", text: "Log in" + end + end + end - expect(URI.parse(current_url).path).to eq registration_auth_path + def click_login_and_ensure_content(content) + # Buttons appear to be unresponsive for a while, so keep clicking them until content appears + using_wait_time 1 do + 3.times do + click_button "Log in" + break if page.has_selector? "div#loading", text: "Hold on a moment, we're logging you in" + end + end + expect(page).to have_content content + end - sleep 0.5 # TOTO: DEAL WITH ME - - # Logging in - click_link "Log in" - fill_in "Email", with: user.email - fill_in "Password", with: user.password - click_button 'Log in' - - # Log in was successful, introduction shown - sleep 0.5 # TOTO: DEAL WITH ME - - expect(page).to have_content "This wizard will step you through creating a profile" - expect(URI.parse(current_url).path).to eq store_registration_path - - # Done reading introduction - click_button "Let's get started!" - - # Details Page - expect(page).to have_content "Woot! First we need to know the name of your farm:" - expect(page).to_not have_selector '#enterprise-types' - - # Everything from here should be covered in 'profile' spec + def click_button_and_ensure_content(button_text, content) + # Buttons appear to be unresponsive for a while, so keep clicking them until content appears + using_wait_time 0.5 do + 10.times do + click_button button_text + break if page.has_content? content + end end end end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 56c578f91b..d4db6bff29 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -167,7 +167,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path end - it "should save group buy data to ze cart" do + it "should save group buy data to the cart" do fill_in "variants[#{variant.id}]", with: 6 fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7 page.should have_in_cart product.name diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 8c611081a2..10db2226a4 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -1,13 +1,15 @@ //= require angular //= require angular-resource //= require angular-animate -//= require angular-sanitize //= require angular-mocks //= require angular-cookies //= require angular-backstretch.js +//= require angularjs-file-upload //= require lodash.underscore.js //= require angular-flash.min.js //= require shared/mm-foundation-tpls-0.2.2.min.js +//= require textAngular.min.js +//= require textAngular-sanitize.min.js //= require moment angular.module('templates', []) 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 b1b019276b..f6805fd04d 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 @@ -4,10 +4,14 @@ describe "enterpriseCtrl", -> Enterprise = null PaymentMethods = null ShippingMethods = null + longDescriptionMock = ["long description text"] beforeEach -> module('admin.enterprises') - Enterprise = + module ($provide)-> + $provide.value "longDescription", longDescriptionMock + null + Enterprise = enterprise: payment_method_ids: [ 1, 3 ] shipping_method_ids: [ 2, 4 ] @@ -15,7 +19,7 @@ describe "enterpriseCtrl", -> paymentMethods: [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 } ] ShippingMethods = shippingMethods: [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 } ] - + inject ($controller) -> scope = {} ctrl = $controller 'enterpriseCtrl', {$scope: scope, Enterprise: Enterprise, PaymentMethods: PaymentMethods, ShippingMethods: ShippingMethods} @@ -82,4 +86,4 @@ describe "enterpriseCtrl", -> describe "counting selected shipping methods", -> it "counts only shipping methods with selected: true", -> scope.ShippingMethods = [ { selected: true }, { selected: true }, { selected: false }, { selected: true } ] - expect(scope.selectedShippingMethodsCount()).toBe 3 \ No newline at end of file + expect(scope.selectedShippingMethodsCount()).toBe 3 diff --git a/spec/javascripts/unit/darkswarm/filters/filter_hubs_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/filter_hubs_spec.js.coffee deleted file mode 100644 index fc2702a72b..0000000000 --- a/spec/javascripts/unit/darkswarm/filters/filter_hubs_spec.js.coffee +++ /dev/null @@ -1,45 +0,0 @@ -describe 'filtering Hubs', -> - filter = null - filterHubs = null - hubs = [{ - name: "frogs" - other: "roger" - address: - zipcode: "cats" - city: "cambridge" - state: "kansas" - }, { - name: "donkeys" - other: "roger" - address: - zipcode: "" - city: "Wellington" - state: "uzbekistan" - }] - - beforeEach -> - module 'Darkswarm' - inject ($filter) -> - filter = $filter - filterHubs = $filter('hubs') - - it 'has a hub filter', -> - expect(filter('hubs')).not.toBeNull() - - it "filters by name", -> - expect(filterHubs(hubs, 'donkeys').length).toEqual 1 - - it "is case insensitive", -> - expect(filterHubs(hubs, 'DONKEYS').length).toEqual 1 - - it "filters by state", -> - expect(filterHubs(hubs, 'kansas').length).toEqual 1 - - it "filters by zipcode", -> - expect(filterHubs(hubs, 'cats').length).toEqual 1 - - it "gives all hubs when no argument is specified", -> - expect(filterHubs(hubs, '').length).toEqual 2 - - it "does not filter by anything else", -> - expect(filterHubs(hubs, 'roger').length).toEqual 0 diff --git a/spec/javascripts/unit/darkswarm/filters/filter_producers_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/filter_producers_spec.js.coffee deleted file mode 100644 index 29d8986b56..0000000000 --- a/spec/javascripts/unit/darkswarm/filters/filter_producers_spec.js.coffee +++ /dev/null @@ -1,28 +0,0 @@ -describe 'filtering producers', -> - filter = null - filterProducers = null - producers = [{ - name: "frogs" - other: "roger" - address: - zipcode: "cats" - city: "cambridge" - state: "kansas" - }, { - name: "donkeys" - other: "roger" - address: - zipcode: "" - city: "Wellington" - state: "uzbekistan" - }] - - beforeEach -> - module 'Darkswarm' - inject ($filter) -> - filter = $filter - filterProducers = $filter('filterProducers') - - - it 'has a producer filter', -> - expect(filter('filterProducers')).not.toBeNull() diff --git a/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee new file mode 100644 index 0000000000..0d21c7de6c --- /dev/null +++ b/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee @@ -0,0 +1,42 @@ +describe 'convert number to localised currency ', -> + filter = currencyconfig = null + + beforeEach -> + currencyconfig = + symbol: "$" + symbol_position: "before" + currency: "D" + hide_cents: "false" + # Not used yet... + # decimal_mark: "." + # thousands_separator: "," + module 'Darkswarm' + module ($provide)-> + $provide.value "currencyConfig", currencyconfig + null + inject ($filter) -> + filter = $filter('localizeCurrency') + + it "adds decimal fraction to an amount", -> + expect(filter(10)).toEqual "$10.00" + + it "handles an existing fraction", -> + expect(filter(9.9)).toEqual "$9.90" + + it "can use any currency symbol", -> + currencyconfig.symbol = "£" + expect(filter(404.04)).toEqual "£404.04" + + it "can place symbols after the amount", -> + currencyconfig.symbol_position = "after" + expect(filter(333.3)).toEqual "333.30 $" + + it "can add a currency string", -> + currencyconfig.display_currency = "true" + expect(filter(5)).toEqual "$5.00 D" + + it "can hide cents", -> + currencyconfig.hide_cents = "true" + expect(filter(5)).toEqual "$5" + + diff --git a/spec/javascripts/unit/darkswarm/filters/search_enterprises_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/search_enterprises_spec.js.coffee new file mode 100644 index 0000000000..2717e50cfb --- /dev/null +++ b/spec/javascripts/unit/darkswarm/filters/search_enterprises_spec.js.coffee @@ -0,0 +1,40 @@ +describe 'filtering Enterprises', -> + filter = null + enterprises = [{ + name: "frogs" + other: "roger" + address: + zipcode: "cats" + city: "cambridge" + state: "kansas" + }, { + name: "donkeys" + other: "roger" + address: + zipcode: "" + city: "Wellington" + state: "uzbekistan" + }] + + beforeEach -> + module 'Darkswarm' + inject ($filter) -> + filter = $filter('searchEnterprises') + + it "filters by name", -> + expect(filter(enterprises, 'donkeys').length).toEqual 1 + + it "is case insensitive", -> + expect(filter(enterprises, 'DONKEYS').length).toEqual 1 + + it "filters by state", -> + expect(filter(enterprises, 'kansas').length).toEqual 1 + + it "filters by zipcode", -> + expect(filter(enterprises, 'cats').length).toEqual 1 + + it "gives all enterprises when no argument is specified", -> + expect(filter(enterprises, '').length).toEqual 2 + + it "does not filter by anything else", -> + expect(filter(enterprises, 'roger').length).toEqual 0 diff --git a/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee index 59d4ae9826..94dd7d39d2 100644 --- a/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee @@ -1,24 +1,30 @@ describe "Enterprises service", -> Enterprises = null - CurrentHubMock = {} + CurrentHubMock = {} taxons = [ {id: 1, name: "test"} ] enterprises = [ - {id: 1, type: "hub", producers: [{id: 2}], taxons: [{id: 1}]}, - {id: 2, type: "producer", hubs: [{id: 1}]}, - {id: 3, type: "producer", hubs: [{id: 1}]} + {id: 1, visible: true, category: "hub", producers: [{id: 5}], taxons: [{id: 1}]}, + {id: 2, visible: true, category: "hub", producers: [{id: 6}]} + {id: 3, visible: true, category: "hub_profile"} + {id: 4, visible: false, category: "hub", producers: [{id: 7}]} + {id: 5, visible: true, category: "producer_hub", hubs: [{id: 1}]}, + {id: 6, visible: true, category: "producer_shop", hubs: [{id: 2}]}, + {id: 7, visible: true, category: "producer", hubs: [{id: 2}]} + {id: 8, visible: false, category: "producer", hubs: [{id: 2}]} ] + H1: 0 beforeEach -> module 'Darkswarm' module ($provide)-> - $provide.value "CurrentHub", CurrentHubMock + $provide.value "CurrentHub", CurrentHubMock null - angular.module('Darkswarm').value('enterprises', enterprises) - angular.module('Darkswarm').value('taxons', taxons) + angular.module('Darkswarm').value('enterprises', enterprises) + angular.module('Darkswarm').value('taxons', taxons) inject ($injector)-> - Enterprises = $injector.get("Enterprises") + Enterprises = $injector.get("Enterprises") it "stores enterprises as id/object pairs", -> expect(Enterprises.enterprises_by_id["1"]).toBe enterprises[0] @@ -31,8 +37,39 @@ describe "Enterprises service", -> expect(Enterprises.enterprises[0]).toBe Enterprises.enterprises_by_id["1"] it "dereferences references to other enterprises", -> - expect(Enterprises.enterprises_by_id["1"].producers[0]).toBe enterprises[1] - expect(Enterprises.enterprises_by_id["3"].hubs[0]).toBe enterprises[0] + expect(Enterprises.enterprises_by_id["1"].producers[0]).toBe enterprises[4] + expect(Enterprises.enterprises_by_id["5"].hubs[0]).toBe enterprises[0] it "dereferences taxons", -> expect(Enterprises.enterprises[0].taxons[0]).toBe taxons[0] + + it "filters Enterprise.hubs into a new array", -> + expect(Enterprises.hubs[0]).toBe Enterprises.enterprises[0] + # Because the $filter is a new sorted array + # We check to see the objects in both arrays are still the same + Enterprises.enterprises[0].active = false + expect(Enterprises.hubs[0].active).toBe false + + it "filters Enterprises.producers into a new array", -> + expect(Enterprises.producers[0]).toBe Enterprises.enterprises[4] + Enterprises.enterprises[4].active = false + expect(Enterprises.producers[0].active).toBe false + + it "only includes visible enterprises in hubs array", -> + expect(Enterprises.hubs).toContain Enterprises.enterprises[0] + expect(Enterprises.hubs).not.toContain Enterprises.enterprises[3] + + it "only includes visible enterprises in producers array", -> + expect(Enterprises.producers).toContain Enterprises.enterprises[4] + expect(Enterprises.producers).not.toContain Enterprises.enterprises[7] + + it "includes hub, hub_profile, producer_hub and, producer_shop enterprises in hubs array", -> + expect(Enterprises.hubs).toContain Enterprises.enterprises[0] + expect(Enterprises.hubs).toContain Enterprises.enterprises[2] + expect(Enterprises.hubs).toContain Enterprises.enterprises[4] + expect(Enterprises.hubs).toContain Enterprises.enterprises[5] + + it "includes producer_hub, producer_shop and producer enterprises in producers array", -> + expect(Enterprises.producers).toContain Enterprises.enterprises[4] + expect(Enterprises.producers).toContain Enterprises.enterprises[5] + expect(Enterprises.producers).toContain Enterprises.enterprises[6] diff --git a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee deleted file mode 100644 index f8ae8230bc..0000000000 --- a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee +++ /dev/null @@ -1,46 +0,0 @@ -describe "Hubs service", -> - Hubs = null - Enterprises = null - CurrentHubMock = {} - hubs = [ - { - id: 2 - active: false - orders_close_at: new Date() - is_distributor: true - has_shopfront: true - } - { - id: 3 - active: false - orders_close_at: new Date() - is_distributor: true - has_shopfront: true - } - { - id: 1 - active: true - orders_close_at: new Date() - is_distributor: true - has_shopfront: true - } - ] - - - beforeEach -> - module 'Darkswarm' - angular.module('Darkswarm').value('enterprises', hubs) - module ($provide)-> - $provide.value "CurrentHub", CurrentHubMock - null - inject ($injector)-> - Enterprises = $injector.get("Enterprises") - Hubs = $injector.get("Hubs") - - it "filters Enterprise.hubs into a new array", -> - expect(Hubs.hubs[0]).toBe Enterprises.enterprises[2] - # Because the $filter is a new sorted array - # We check to see the objects in both arrays are still the same - Enterprises.enterprises[2].active = false - expect(Hubs.hubs[0].active).toBe false - diff --git a/spec/javascripts/unit/darkswarm/services/producers_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/producers_spec.js.coffee deleted file mode 100644 index dc9ac0fc8f..0000000000 --- a/spec/javascripts/unit/darkswarm/services/producers_spec.js.coffee +++ /dev/null @@ -1,21 +0,0 @@ -describe "Producers service", -> - Producers = null - Enterprises = null - CurrentHubMock = - hub: - id: 1 - enterprises = [ - {is_primary_producer: true} - ] - - beforeEach -> - module 'Darkswarm' - module ($provide)-> - $provide.value "CurrentHub", CurrentHubMock - null - angular.module('Darkswarm').value('enterprises', enterprises) - inject ($injector)-> - Producers = $injector.get("Producers") - - it "delegates producers array to Enterprises", -> - expect(Producers.producers[0]).toBe enterprises[0] diff --git a/spec/javascripts/unit/darkswarm/services/variants_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/variants_spec.js.coffee index ac9865a142..5c6e138e0d 100644 --- a/spec/javascripts/unit/darkswarm/services/variants_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/variants_spec.js.coffee @@ -5,8 +5,8 @@ describe 'Variants service', -> beforeEach -> variant = id: 1 - base_price: 80.5 - price: 100 + price: 80.5 + price_with_fees: 100 module 'Darkswarm' inject ($injector)-> Variants = $injector.get("Variants") diff --git a/spec/mailers/enterprise_mailer_spec.rb b/spec/mailers/enterprise_mailer_spec.rb index 412870bad7..dd1e150eb7 100644 --- a/spec/mailers/enterprise_mailer_spec.rb +++ b/spec/mailers/enterprise_mailer_spec.rb @@ -1,13 +1,16 @@ require 'spec_helper' describe EnterpriseMailer do + let!(:enterprise) { create(:enterprise) } + before do - @enterprise = create(:enterprise) ActionMailer::Base.deliveries = [] end - it "should send an email when given an enterprise" do - EnterpriseMailer.creation_confirmation(@enterprise).deliver + it "should send an email confirmation when given an enterprise" 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}" end end \ No newline at end of file diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index f6b3c13d2d..206be1e131 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -3,6 +3,25 @@ require 'spec_helper' describe Enterprise do include AuthenticationWorkflow + describe "sending emails" do + describe "on creation" do + let!(:user) { create_enterprise_user( enterprise_limit: 2 ) } + let!(:enterprise) { create(:enterprise, owner: user) } + + it "when the email address has not already been confirmed" do + mail_message = double "Mail::Message" + EnterpriseMailer.should_receive(:confirmation_instructions).and_return mail_message + mail_message.should_receive :deliver + create(:enterprise, owner: user, email: "unknown@email.com", confirmed_at: nil ) + end + + it "when the email address has already been confirmed" do + EnterpriseMailer.should_not_receive(:confirmation_instructions) + e = create(:enterprise, owner: user, email: enterprise.email, confirmed_at: nil) + end + end + end + describe "associations" do it { should belong_to(:owner) } it { should have_many(:supplied_products) } @@ -85,6 +104,8 @@ describe Enterprise do describe "validations" do subject { FactoryGirl.create(:distributor_enterprise, :address => FactoryGirl.create(:address)) } it { should validate_presence_of(:name) } + it { should validate_presence_of(:email) } + it { should ensure_length_of(:description).is_at_most(255) } it "requires an owner" do expect{ @@ -101,6 +122,7 @@ describe Enterprise do it { should delegate(:city).to(:address) } it { should delegate(:state_name).to(:address) } end + describe "scopes" do describe 'active' do it 'find active enterprises' do @@ -110,6 +132,28 @@ describe Enterprise do end end + describe "confirmed" do + it "find enterprises with a confirmed date" do + s1 = create(:supplier_enterprise) + d1 = create(:distributor_enterprise) + s2 = create(:supplier_enterprise, confirmed_at: nil) + d2 = create(:distributor_enterprise, confirmed_at: nil) + expect(Enterprise.confirmed).to include s1, d1 + expect(Enterprise.confirmed).to_not include s2, d2 + end + end + + describe "unconfirmed" do + it "find enterprises without a confirmed date" do + s1 = create(:supplier_enterprise) + d1 = create(:distributor_enterprise) + s2 = create(:supplier_enterprise, confirmed_at: nil) + d2 = create(:distributor_enterprise, confirmed_at: nil) + expect(Enterprise.unconfirmed).to_not include s1, d1 + expect(Enterprise.unconfirmed).to include s2, d2 + end + end + describe "distributors_with_active_order_cycles" do it "finds active distributors by order cycles" do s = create(:supplier_enterprise) @@ -501,4 +545,25 @@ describe Enterprise do supplier.producer_properties.first.property.presentation.should == 'Organic Certified' end end + + describe "provide enterprise category" do + let(:producer_sell_all) { build(:enterprise, is_primary_producer: true, sells: "any") } + let(:producer_sell_own) { build(:enterprise, is_primary_producer: true, sells: "own") } + let(:producer_sell_none) { build(:enterprise, is_primary_producer: true, sells: "none") } + let(:non_producer_sell_all) { build(:enterprise, is_primary_producer: false, sells: "any") } + let(:non_producer_sell_own) { build(:enterprise, is_primary_producer: false, sells: "own") } + let(:non_producer_sell_none) { build(:enterprise, is_primary_producer: false, sells: "none") } + + it "should output enterprise categories" do + producer_sell_all.is_primary_producer.should == true + producer_sell_all.sells.should == "any" + + producer_sell_all.category.should == :producer_hub + producer_sell_own.category.should == :producer_shop + producer_sell_none.category.should == :producer + non_producer_sell_all.category.should == :hub + non_producer_sell_own.category.should == :hub + non_producer_sell_none.category.should == :hub_profile + end + end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 9785d5ed2d..56aa51a0d0 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -44,8 +44,8 @@ describe OrderCycle do end it "finds order cycles accessible by a user" do - e1 = create(:enterprise, is_primary_producer: true, is_distributor: true) - e2 = create(:enterprise, is_primary_producer: true, is_distributor: true) + e1 = create(:enterprise, is_primary_producer: true, sells: "any") + e2 = create(:enterprise, is_primary_producer: true, sells: "any") user = create(:user, enterprises: [e2], spree_roles: []) user.spree_roles = [] diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index cf17d7fe3a..e523cce755 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -9,39 +9,81 @@ module Spree describe "broad permissions" do subject { AbilityDecorator.new(user) } let(:user) { create(:user) } - let(:enterprise_full) { create(:enterprise, type: 'full') } - let(:enterprise_single) { create(:enterprise, type: 'single') } - let(:enterprise_profile) { create(:enterprise, type: 'profile') } + let(:enterprise_any) { create(:enterprise, sells: 'any') } + let(:enterprise_own) { create(:enterprise, sells: 'own') } + let(:enterprise_none) { create(:enterprise, sells: 'none') } + let(:enterprise_any_producer) { create(:enterprise, sells: 'any', is_primary_producer: true) } + let(:enterprise_own_producer) { create(:enterprise, sells: 'own', is_primary_producer: true) } + let(:enterprise_none_producer) { create(:enterprise, sells: 'none', is_primary_producer: true) } - describe "managing enterprises" do - it "can manage enterprises when the user has at least one enterprise assigned" do - user.enterprise_roles.create! enterprise: enterprise_full - subject.can_manage_enterprises?(user).should be_true + context "as manager of an enterprise who sells 'any'" do + before do + user.enterprise_roles.create! enterprise: enterprise_any end - it "can't otherwise" do - subject.can_manage_enterprises?(user).should be_false - end + it { subject.can_manage_products?(user).should be_false } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_true } end - describe "managing products" do - it "can when a user manages a 'full' type enterprise" do - user.enterprise_roles.create! enterprise: enterprise_full - subject.can_manage_products?(user).should be_true + context "as manager of an enterprise who sell 'own'" do + before do + user.enterprise_roles.create! enterprise: enterprise_own end - it "can when a user manages a 'single' type enterprise" do - user.enterprise_roles.create! enterprise: enterprise_single - subject.can_manage_products?(user).should be_true + it { subject.can_manage_products?(user).should be_false } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_true } + end + + context "as manager of an enterprise who sells 'none'" do + before do + user.enterprise_roles.create! enterprise: enterprise_none end - it "can't when a user manages a 'profile' type enterprise" do - user.enterprise_roles.create! enterprise: enterprise_profile - subject.can_manage_products?(user).should be_false + it { subject.can_manage_products?(user).should be_false } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_false } + end + + context "as manager of a producer enterprise who sells 'any'" do + before do + user.enterprise_roles.create! enterprise: enterprise_any_producer end - it "can't when the user manages no enterprises" do - subject.can_manage_products?(user).should be_false + it { subject.can_manage_products?(user).should be_true } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_true } + end + + context "as manager of a producer enterprise who sell 'own'" do + before do + user.enterprise_roles.create! enterprise: enterprise_own_producer + end + + it { subject.can_manage_products?(user).should be_true } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_true } + end + + context "as manager of a producer enterprise who sells 'none'" do + before do + user.enterprise_roles.create! enterprise: enterprise_none_producer + end + + it { subject.can_manage_products?(user).should be_true } + it { subject.can_manage_enterprises?(user).should be_true } + it { subject.can_manage_orders?(user).should be_false } + end + + context "as a new user with no enterprises" do + it { subject.can_manage_products?(user).should be_false } + it { subject.can_manage_enterprises?(user).should be_false } + it { subject.can_manage_orders?(user).should be_false } + + it "can create enterprises straight off the bat" do + subject.is_new_user?(user).should be_true + expect(user).to have_ability :create, for: Enterprise end end end @@ -142,6 +184,14 @@ module Spree should_not have_ability(:destroy, for: er2) end + it "should be able to read some reports" do + should have_ability([:admin, :index, :customers, :bulk_coop, :orders_and_fulfillment, :products_and_inventory], for: :report) + end + + it "should not be able to read other reports" do + should_not have_ability([:sales_total, :group_buys, :payments, :orders_and_distributors], for: :report) + end + end context "when is a distributor enterprise user" do @@ -228,17 +278,26 @@ module Spree it "should not be able to destroy enterprise relationships for other enterprises" do should_not have_ability(:destroy, for: er1) end + + it "should be able to read some reports" do + should have_ability([:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], for: :report) + end + + it "should not be able to read other reports" do + should_not have_ability([:sales_total], for: :report) + end + end - context 'Order Cycle co-ordinator' do - + context 'Order Cycle co-ordinator, distributor enterprise manager' do let (:user) do user = create(:user) user.spree_roles = [] - s1.enterprise_roles.build(user: user).save + d1.enterprise_roles.build(user: user).save user end - let(:oc1) { create(:simple_order_cycle, {coordinator: s1}) } + + let(:oc1) { create(:simple_order_cycle, {coordinator: d1}) } let(:oc2) { create(:simple_order_cycle) } it "should be able to read/write OrderCycles they are the co-ordinator of" do @@ -256,6 +315,10 @@ module Spree it "should be able to read/write EnterpriseFees" do should have_ability([:admin, :index, :read, :create, :edit, :bulk_update, :destroy], for: EnterpriseFee) end + + it "should be able to add enterprises to order cycles" do + should have_ability([:admin, :index, :for_order_cycle, :create], for: Enterprise) + end end context 'enterprise manager' do @@ -275,7 +338,7 @@ module Spree end it 'should have the ability administrate and create enterpises' do - should have_ability([:admin, :index, :for_order_cycle, :create], for: Enterprise) + should have_ability([:admin, :index, :create], for: Enterprise) end end end diff --git a/spec/models/spree/image_spec.rb b/spec/models/spree/image_spec.rb new file mode 100644 index 0000000000..56665fa641 --- /dev/null +++ b/spec/models/spree/image_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +module Spree + describe Image do + describe "attachment definitions" do + let(:name_str) { {"mini" => "48x48>"} } + let(:formatted) { {mini: ["48x48>", "png"]} } + + it "converts style names to symbols" do + Image.format_styles(name_str).should == {:mini => "48x48>"} + end + + it "converts formats to symbols" do + Image.format_styles(formatted).should == {:mini => ["48x48>", :png]} + end + end + end +end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index 5fe57a99e2..f3409f2d57 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -13,9 +13,27 @@ describe Api::EnterpriseSerializer do serializer = Api::EnterpriseSerializer.new enterprise serializer.to_json.should match taxon.id.to_s end - + it "will render urls" do serializer = Api::EnterpriseSerializer.new enterprise serializer.to_json.should match "map_005-hub.svg" end + + describe "visibility" do + before do + enterprise.stub(:visible).and_return true + end + + it "is visible when confirmed" do + enterprise.stub(:confirmed?).and_return true + serializer = Api::EnterpriseSerializer.new enterprise + expect(serializer.to_json).to match "\"visible\":true" + end + + it "is not visible when unconfirmed" do + enterprise.stub(:confirmed?).and_return false + serializer = Api::EnterpriseSerializer.new enterprise + expect(serializer.to_json).to match "\"visible\":false" + end + end end diff --git a/spec/support/matchers/table_matchers.rb b/spec/support/matchers/table_matchers.rb index 146002b751..053562b9e4 100644 --- a/spec/support/matchers/table_matchers.rb +++ b/spec/support/matchers/table_matchers.rb @@ -3,20 +3,15 @@ RSpec::Matchers.define :have_table_row do |row| match_for_should do |node| @row = row - false_on_timeout_error do - wait_until { rows_under(node).include? row } - end + node.has_selector? "tr", text: row.join(" ").strip # Check for appearance + rows_under(node).include? row # Robust check of columns end match_for_should_not do |node| @row = row - false_on_timeout_error do - # Without this sleep, we trigger capybara's wait when looking up the table, for the full - # period of default_wait_time. - sleep 0.1 - wait_until { !rows_under(node).include? row } - end + node.has_no_selector? "tr", text: row.join(" ").strip # Check for appearance + !rows_under(node).include? row # Robust check of columns end failure_message_for_should do |text| @@ -27,17 +22,7 @@ RSpec::Matchers.define :have_table_row do |row| "expected not to find table row #{@row}" end - def rows_under(node) node.all('tr').map { |tr| tr.all('th, td').map(&:text) } end - - def false_on_timeout_error - yield - rescue TimeoutError - false - else - true - end - end diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index a2ee597f27..39f78223d7 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -37,12 +37,9 @@ module AuthenticationWorkflow visit spree.admin_path end - def create_enterprise_user(enterprises = []) - new_user = create(:user, password: 'blahblah', :password_confirmation => 'blahblah') + def create_enterprise_user( attrs = {} ) + new_user = create(:user, attrs) new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default. - for enterprise in enterprises do - new_user.enterprise_roles.build(enterprise: enterprise).save - end new_user.save new_user end diff --git a/vendor/assets/javascripts/textAngular-sanitize.min.js b/vendor/assets/javascripts/textAngular-sanitize.min.js new file mode 100644 index 0000000000..75534aaa01 --- /dev/null +++ b/vendor/assets/javascripts/textAngular-sanitize.min.js @@ -0,0 +1 @@ +!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 diff --git a/vendor/assets/javascripts/textAngular.min.js b/vendor/assets/javascripts/textAngular.min.js new file mode 100644 index 0000000000..aba313ad9f --- /dev/null +++ b/vendor/assets/javascripts/textAngular.min.js @@ -0,0 +1,2 @@ +!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:"