diff --git a/.gitignore b/.gitignore index 42107a8e2e..13f5a4441c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ NERD_tree* coverage libpeerconnection.log /config/application.yml +node_modules diff --git a/.travis.yml b/.travis.yml index 56d94e5952..ef54e640c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,13 +26,16 @@ before_script: - cp config/database.travis.yml config/database.yml - cp config/application.yml.example config/application.yml - RAILS_ENV=test bundle exec rake db:create db:schema:load + + # Only install PhantomJS if it is not already present (ie. cached) + - npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7 + - export PATH=`npm bin -g`:$PATH + - > if [ "$KARMA" = "true" ]; then - npm install karma@0.12.31 - npm install karma-jasmine@0.1.5 - npm install karma-phantomjs-launcher@0.1.4 - npm install karma-coffee-preprocessor@0.2.1 - npm install -g karma-cli@0.0.4 + npm install -g npm@'3.8.8' + npm install + npm install -g karma-cli@0.1.2 fi script: diff --git a/Gemfile b/Gemfile index d581c8d10b..eb49f89c23 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa' gem 'simple_form', :github => 'RohanM/simple_form' gem 'unicorn' -gem 'angularjs-rails', '1.2.13' +gem 'angularjs-rails', '1.5.5' gem 'bugsnag' gem 'newrelic_rpm' gem 'haml' diff --git a/Gemfile.lock b/Gemfile.lock index 0e31157d96..07931312ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM sprockets (~> 2) tilt angularjs-file-upload-rails (1.1.0) - angularjs-rails (1.2.13) + angularjs-rails (1.5.5) ansi (1.4.2) arel (3.0.3) atomic (1.1.99) @@ -176,7 +176,8 @@ GEM columnize (~> 0.3) debugger-linecache (~> 1.2) cancan (1.6.8) - capybara (2.5.0) + capybara (2.7.1) + addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -455,7 +456,7 @@ GEM railties (>= 3.1) money (5.1.1) i18n (~> 0.6.0) - multi_json (1.11.2) + multi_json (1.12.0) multi_xml (0.5.5) newrelic_rpm (3.12.0.288) nokogiri (1.6.7.2) @@ -479,7 +480,7 @@ GEM paypal-sdk-merchant (1.106.1) paypal-sdk-core (~> 0.2.3) pg (0.13.2) - poltergeist (1.7.0) + poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) multi_json (~> 1.0) @@ -626,7 +627,7 @@ GEM webmock (1.13.0) addressable (>= 2.2.7) crack (>= 0.3.2) - websocket-driver (0.6.2) + websocket-driver (0.6.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) whenever (0.9.2) @@ -650,7 +651,7 @@ DEPENDENCIES andand angular-rails-templates (~> 0.2.0) angularjs-file-upload-rails (~> 1.1.0) - angularjs-rails (= 1.2.13) + angularjs-rails (= 1.5.5) atomic awesome_print aws-sdk @@ -734,4 +735,4 @@ DEPENDENCIES wkhtmltopdf-binary BUNDLED WITH - 1.10.6 + 1.11.2 diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 9f99dc1dcd..0923e5721c 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -43,9 +43,9 @@ //= require ./utils/utils //= require ./users/users //= require ./variant_overrides/variant_overrides -//= require textAngular.min.js +//= require textAngular-rangy.min.js //= require textAngular-sanitize.min.js -//= require ../shared/bindonce.min.js +//= require textAngular.min.js //= require darkswarm/i18n.js //= require darkswarm/i18n.translate.js diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee deleted file mode 100644 index b747d3699e..0000000000 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ /dev/null @@ -1,222 +0,0 @@ -angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ - "$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns" - ($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) -> - $scope.loading = true - - $scope.initialiseVariables = -> - start = daysFromToday -7 - end = daysFromToday 1 - $scope.lineItems = [] - $scope.filteredLineItems = [] - $scope.confirmDelete = true - $scope.startDate = formatDate start - $scope.endDate = formatDate end - $scope.quickSearch = "" - $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ] - $scope.selectedBulkAction = $scope.bulkActions[0] - $scope.selectedUnitsProduct = {}; - $scope.selectedUnitsVariant = {}; - $scope.sharedResource = false - $scope.columns = Columns.setColumns - order_no: { name: t("bom_no"), visible: false } - full_name: { name: t("name"), visible: true } - email: { name: t("email"), visible: false } - phone: { name: t("phone"), visible: false } - order_date: { name: t("bom_date"), visible: true } - producer: { name: t("producer"), visible: true } - order_cycle: { name: t("bom_cycle"), visible: false } - hub: { name: t("bom_hub"), visible: false } - variant: { name: t("bom_variant"), visible: true } - quantity: { name: t("bom_quantity"), visible: true } - max: { name: t("bom_max"), visible: true } - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } - price: { name: t("price"), visible: false } - $scope.initialise = -> - $scope.initialiseVariables() - authorise_api_reponse = "" - dataFetcher("/api/users/authorise_api?token=" + SpreeApiKey).then (data) -> - authorise_api_reponse = data - $scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised" - if $scope.spree_api_key_ok - $http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey - dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> - $scope.suppliers = $filter('orderBy')(data, 'name') - $scope.suppliers.unshift blankOption() - dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) -> - $scope.distributors = $filter('orderBy')(data, 'name') - $scope.distributors.unshift blankOption() - ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) -> - $scope.orderCycles = data - $scope.orderCyclesByID = [] - $scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles - $scope.orderCycles.unshift blankOption() - $scope.fetchOrders() - ocFetcher.then -> - $scope.resetSelectFilters() - else if authorise_api_reponse.hasOwnProperty("error") - $scope.api_error_msg = authorise_api_reponse("error") - else - api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access." - - $scope.fetchOrders = -> - $scope.loading = true - dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> - $scope.resetOrders data - $scope.loading = false - - $scope.resetOrders = (data) -> - $scope.orders = data - $scope.resetLineItems() - pendingChanges.removeAll() - - $scope.resetLineItems = -> - $scope.lineItems = $scope.orders.reduce (lineItems,order) -> - orderWithoutLineItems = $scope.lineItemOrder order - for i,line_item of order.line_items - line_item.checked = false - line_item.supplier = $scope.matchObject $scope.suppliers, line_item.supplier, null - line_item.order = orderWithoutLineItems - line_item.original_final_weight_volume = line_item.final_weight_volume - line_item.original_quantity = line_item.quantity - line_item.original_price = line_item.price - - lineItems.concat order.line_items - , [] - - $scope.lineItemOrder = (order) -> - lineItemOrder = angular.copy(order) - delete lineItemOrder.line_items - lineItemOrder.distributor = $scope.matchObject $scope.distributors, order.distributor, null - lineItemOrder.order_cycle = $scope.matchObject $scope.orderCycles, order.order_cycle, null - lineItemOrder - - $scope.matchObject = (list, testObject, noMatch) -> - for i, object of list - if angular.equals(object, testObject) - return object - return noMatch - - $scope.deleteLineItem = (lineItem) -> - if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete - $http( - method: "DELETE" - url: "/api/orders/" + lineItem.order.number + "/line_items/" + lineItem.id - ).success (data) -> - $scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1 - - $scope.deleteLineItems = (lineItems) -> - existingState = $scope.confirmDelete - $scope.confirmDelete = false - $scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked - $scope.confirmDelete = existingState - - $scope.submit = -> - if $scope.bulk_order_form.$valid - pendingChanges.submitAll() - else - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." - - $scope.allBoxesChecked = -> - checkedCount = $scope.filteredLineItems.reduce (count,lineItem) -> - count + (if lineItem.checked then 1 else 0 ) - , 0 - checkedCount == $scope.filteredLineItems.length - - $scope.toggleAllCheckboxes = -> - changeTo = !$scope.allBoxesChecked() - lineItem.checked = changeTo for lineItem in $scope.filteredLineItems - - $scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) -> - $scope.selectedUnitsProduct = unitsProduct - $scope.selectedUnitsVariant = unitsVariant - - $scope.sumUnitValues = -> - sum = $scope.filteredLineItems.reduce (sum,lineItem) -> - sum = sum + lineItem.final_weight_volume - , 0 - - $scope.sumMaxUnitValues = -> - sum = $scope.filteredLineItems.reduce (sum,lineItem) -> - sum = sum + Math.max(lineItem.max_quantity,lineItem.original_quantity) * lineItem.units_variant.unit_value - , 0 - - $scope.allFinalWeightVolumesPresent = -> - for i,lineItem of $scope.filteredLineItems - return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0) - true - - # How is this different to OptionValueNamer#name? - # Should it be extracted to that class or VariantUnitManager? - $scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) -> - # A Units Variant is an API object which holds unit properies of a variant - if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume") && value > 0 - scale = VariantUnitManager.getScale(value, unitsProduct.variant_unit) - Math.round(value/scale * 1000)/1000 + " " + VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit) - else - '' - - $scope.fulfilled = (sumOfUnitValues) -> - # A Units Variant is an API object which holds unit properies of a variant - if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 && - $scope.selectedUnitsProduct.hasOwnProperty("variant_unit") && - ( $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume" ) - Math.round( sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size * 1000)/1000 - else - '' - - $scope.unitsVariantSelected = -> - !angular.equals($scope.selectedUnitsVariant,{}) - - $scope.resetSelectFilters = -> - $scope.distributorFilter = $scope.distributors[0].id - $scope.supplierFilter = $scope.suppliers[0].id - $scope.orderCycleFilter = $scope.orderCycles[0].id - $scope.quickSearch = "" - - $scope.weightAdjustedPrice = (lineItem) -> - if lineItem.final_weight_volume > 0 - unit_value = lineItem.final_weight_volume / lineItem.quantity - original_unit_value = lineItem.original_final_weight_volume / lineItem.original_quantity - lineItem.price = lineItem.original_price * (unit_value / original_unit_value) - - $scope.unitValueLessThanZero = (lineItem) -> - if lineItem.units_variant.unit_value <= 0 - true - else - false - - $scope.updateOnQuantity = (lineItem) -> - if lineItem.quantity > 0 - lineItem.final_weight_volume = lineItem.original_final_weight_volume * lineItem.quantity / lineItem.original_quantity - $scope.weightAdjustedPrice(lineItem) - - $scope.$watch "orderCycleFilter", (newVal, oldVal) -> - unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal) - $scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order - $scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order -] - -daysFromToday = (days) -> - now = new Date - now.setHours(0) - now.setMinutes(0) - now.setSeconds(0) - now.setDate( now.getDate() + days ) - now - -formatDate = (date) -> - year = date.getFullYear() - month = twoDigitNumber date.getMonth() + 1 - day = twoDigitNumber date.getDate() - return year + "-" + month + "-" + day - -formatTime = (date) -> - hours = twoDigitNumber date.getHours() - mins = twoDigitNumber date.getMinutes() - secs = twoDigitNumber date.getSeconds() - return hours + ":" + mins + ":" + secs - -twoDigitNumber = (number) -> - twoDigits = "" + number - twoDigits = ("0" + number) if number < 10 - twoDigits diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 3d565f703a..751734a899 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -3,18 +3,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.StatusMessage = StatusMessage - $scope.columns = Columns.setColumns - producer: {name: t("products_producer"), visible: true} - sku: {name: t("products_sku"), visible: false} - name: {name: t("products_name"), visible: true} - unit: {name: t("products_unit"), visible: true} - price: {name: t("products_price"), visible: true} - on_hand: {name: t("products_on_hand"), visible: true} - on_demand: {name: t("products_on_demand"), visible: false} - category: {name: t("products_category"), visible: false} - tax_category: {name: t("products_tax_category"), visible: false} - inherits_properties: {name: t("products_inherits_properties"), visible: false} - available_on: {name: t("products_available_on"), visible: false} + $scope.columns = Columns.columns $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() diff --git a/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee b/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee index ca757c673d..08eacd7ff2 100644 --- a/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee +++ b/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee @@ -9,6 +9,10 @@ angular.module("admin.businessModelConfiguration").controller "BusinessModelConf return $scope.bill() if !$scope.cap? || Number($scope.cap) == 0 Math.min($scope.bill(), Number($scope.cap)) + $scope.finalBill = -> + return 0 if Number($scope.turnover) < Number($scope.minBillableTurnover) + $scope.cappedBill() + $scope.capReached = -> return "No" if !$scope.cap? || Number($scope.cap) == 0 if $scope.bill() >= Number($scope.cap) then "Yes" else "No" @@ -18,4 +22,4 @@ angular.module("admin.businessModelConfiguration").controller "BusinessModelConf ($scope.cappedBill() * Number($scope.taxRate)) $scope.total = -> - $scope.cappedBill() + $scope.includedTax() + $scope.finalBill() + $scope.includedTax() diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index bfccfd3f4b..23bfb37738 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,43 +1,24 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) -> - $scope.shop = {} +angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) -> $scope.shops = shops + $scope.CurrentShop = CurrentShop + $scope.RequestMonitor = RequestMonitor $scope.submitAll = pendingChanges.submitAll + $scope.add = Customers.add + $scope.deleteCustomer = Customers.remove + $scope.customerLimit = 20 + $scope.columns = Columns.columns - $scope.columns = Columns.setColumns - email: { name: "Email", visible: true } - code: { name: "Code", visible: true } - tags: { name: "Tags", visible: true } - - $scope.$watch "shop.id", -> - if $scope.shop.id? - $scope.customers = index {enterprise_id: $scope.shop.id} + $scope.$watch "CurrentShop.shop", -> + if $scope.CurrentShop.shop.id? + Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) -> + $scope.customers = data $scope.findTags = (query) -> defer = $q.defer() params = - enterprise_id: $scope.shop.id - TagsResource.index params, (data) => + enterprise_id: $scope.CurrentShop.shop.id + TagRuleResource.mapByTag params, (data) => filtered = data.filter (tag) -> tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 defer.resolve filtered defer.promise - - $scope.add = (email) -> - params = - enterprise_id: $scope.shop.id - email: email - CustomerResource.create params, (customer) => - if customer.id - $scope.customers.push customer - $scope.quickSearch = customer.email - - $scope.deleteCustomer = (customer) -> - params = id: customer.id - CustomerResource.destroy params, -> - i = $scope.customers.indexOf customer - $scope.customers.splice i, 1 unless i < 0 - - index = (params) -> - $scope.loaded = false - CustomerResource.index params, => - $scope.loaded = true diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee index 1e8ae9b988..fe8ae1de5b 100644 --- a/app/assets/javascripts/admin/customers/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -1 +1 @@ -angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) \ No newline at end of file +angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) diff --git a/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee new file mode 100644 index 0000000000..02276875b9 --- /dev/null +++ b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee @@ -0,0 +1,46 @@ +angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, $window, CurrentShop, Customers) -> + restrict: 'A' + scope: true + link: (scope, element, attr) -> + scope.CurrentShop = CurrentShop + scope.submitted = null + scope.email = "" + scope.errors = [] + + scope.addCustomer = (valid) -> + scope.submitted = scope.email + scope.errors = [] + if valid + Customers.add(scope.email).$promise.then (data) -> + if data.id + scope.email = "" + scope.submitted = null + template.dialog('close') + , (response) -> + if response.data.errors + scope.errors.push(error) for error in response.data.errors + else + scope.errors.push("Sorry! Could not create '#{scope.email}'") + return + + # Compile modal template + template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope) + + # Set Dialog options + template.dialog + show: { effect: "fade", duration: 400 } + hide: { effect: "fade", duration: 300 } + autoOpen: false + resizable: false + width: $window.innerWidth * 0.4; + modal: true + open: (event, ui) -> + $('.ui-widget-overlay').bind 'click', -> + $(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close') + + # Link opening of dialog to click event on element + element.bind 'click', (e) -> + if CurrentShop.shop.id + template.dialog('open') + else + alert('Please select a shop first') diff --git a/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee new file mode 100644 index 0000000000..9df7e09895 --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee @@ -0,0 +1,3 @@ +angular.module("admin.customers").factory "CurrentShop", -> + new class CurrentShop + shop: {} diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee new file mode 100644 index 0000000000..a9f4f0102f --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee @@ -0,0 +1,21 @@ +angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) -> + new class Customers + customers: [] + + add: (email) -> + params = + enterprise_id: CurrentShop.shop.id + email: email + CustomerResource.create params, (customer) => + @customers.unshift customer if customer.id + + remove: (customer) -> + params = id: customer.id + CustomerResource.destroy params, => + i = @customers.indexOf customer + @customers.splice i, 1 unless i < 0 + + index: (params) -> + request = CustomerResource.index(params, (data) => @customers = data) + RequestMonitor.load(request.$promise) + request.$promise diff --git a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee b/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee deleted file mode 100644 index ad89511e32..0000000000 --- a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee +++ /dev/null @@ -1,9 +0,0 @@ -angular.module("admin.customers").factory 'TagsResource', ($resource) -> - $resource('/admin/tags.json', {}, { - 'index': - method: 'GET' - isArray: true - cache: true - params: - enterprise_id: '@enterprise_id' - }) diff --git a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee new file mode 100644 index 0000000000..a19b6aaed3 --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee @@ -0,0 +1,10 @@ +angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) -> + $scope.columns = Columns.columns + $scope.toggle = Columns.toggleColumn + $scope.saved = Columns.preferencesSaved + $scope.saving = false + + $scope.saveColumnPreferences = (action_name) -> + $scope.saving = true + Columns.savePreferences(action_name).then -> + $scope.saving = false diff --git a/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee new file mode 100644 index 0000000000..8248b626bf --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee @@ -0,0 +1,6 @@ +angular.module("admin.dropdown").directive 'columnsDropdown', -> + restrict: 'E' + templateUrl: 'admin/columns_dropdown.html' + controller: 'ColumnsDropdownCtrl' + scope: + action: '@' diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee index 3d8bfa6446..d6dda4a118 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee @@ -5,9 +5,4 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E $q.all(requests).then -> $scope.loaded = true - $scope.columns = Columns.setColumns - name: { name: "Name", visible: true } - producer: { name: "Producer", visible: true } - package: { name: "Package", visible: true } - status: { name: "Status", visible: true } - manage: { name: "Manage", visible: true } + $scope.columns = Columns.columns diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee index 2074a1ea05..da122761b8 100644 --- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee @@ -1 +1 @@ -angular.module("admin.enterprises", [ "admin.paymentMethods", "admin.utils", "admin.shippingMethods", "admin.users", "textAngular", "admin.side_menu", "admin.taxons", 'admin.indexUtils', 'admin.tagRules', 'admin.dropdown', 'pasvaz.bindonce', 'ngSanitize'] ) \ No newline at end of file +angular.module("admin.enterprises", [ "admin.paymentMethods", "admin.utils", "admin.shippingMethods", "admin.users", "textAngular", "admin.side_menu", "admin.taxons", 'admin.indexUtils', 'admin.tagRules', 'admin.dropdown', 'ngSanitize'] ) \ No newline at end of file diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee index 132480d987..bbb5221682 100644 --- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) -> +angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $filter) -> require: 'ngModel' restrict: 'C' scope: @@ -6,15 +6,19 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) minSearch: "@?" text: "@?" blank: "=?" + filter: "=?" link: (scope, element, attrs, ngModel) -> $timeout -> scope.text ||= 'name' + scope.filter ||= -> true scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" item.name = $sanitize(item.name) for item in scope.data element.select2 minimumResultsForSearch: scope.minSearch || 0 - data: { results: scope.data, text: scope.text } + data: -> + filtered = $filter('filter')(scope.data,scope.filter) + { results: filtered, text: scope.text } formatSelection: (item) -> item[scope.text] formatResult: (item) -> diff --git a/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee new file mode 100644 index 0000000000..a6e4efa333 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.indexUtils").component 'showMore', + templateUrl: 'admin/show_more.html' + bindings: + data: "=" + limit: "=" + increment: "=" + +# For now, this component is not being used. +# Something about binding "data" to a variable on the parent scope that is continually refreshed by +# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved +# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this +# has the limitation of not being able to be limited AFTER the assignment has been made, which we need diff --git a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee deleted file mode 100644 index 614b8d9346..0000000000 --- a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -angular.module("admin.indexUtils").directive "toggleColumn", (Columns) -> - link: (scope, element, attrs) -> - element.addClass "selected" if scope.column.visible - - element.click "click", -> - scope.$apply -> - Columns.toggleColumn(scope.column) - element.toggleClass "selected" diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee index 5e5b5cadf2..1ea74e614b 100644 --- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -1 +1 @@ -angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file +angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee index 8bd99bf2f2..45bea7c680 100644 --- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -1,13 +1,15 @@ -angular.module("admin.indexUtils").factory 'Columns', ($rootScope) -> +angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) -> new class Columns + savedColumns: {} columns: {} visibleCount: 0 - setColumns: (columns) => + constructor: -> @columns = {} - @columns[name] = column for name, column of columns + for column in columns + @columns[column.column_name] = column + @savedColumns[column.column_name] = angular.copy(column) @calculateVisibleCount() - @columns toggleColumn: (column) => column.visible = !column.visible @@ -16,3 +18,18 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope) -> calculateVisibleCount: => @visibleCount = (column for name, column of @columns when column.visible).length $rootScope.$broadcast "columnCount:changed", @visibleCount + + preferencesSaved: => + angular.equals(@columns, @savedColumns) + + savePreferences: (action_name) => + $http + method: "PUT" + url: "/admin/column_preferences/bulk_update" + data: + action_name: action_name + column_preferences: (preference for column_name, preference of @columns) + .success (data) => + for column in data + angular.extend(@columns[column.column_name], column) + angular.extend(@savedColumns[column.column_name], column) diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 2f40a7faef..15626b1479 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -1,10 +1,12 @@ -angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> +angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, StatusMessage) -> new class pendingChanges pendingChanges: {} + errors: [] add: (id, attr, change) => @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") @pendingChanges["#{id}"]["#{attr}"] = change + StatusMessage.display('notice', "You have made #{@changeCount(@pendingChanges)} unsaved changes") removeAll: => @pendingChanges = {} @@ -14,11 +16,19 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> delete @pendingChanges["#{id}"]["#{attr}"] delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: => + submitAll: (form=null) => all = [] + @errors = [] + StatusMessage.display('progress', "Saving...") for id, objectChanges of @pendingChanges for attrName, change of objectChanges all.push @submit(change) + $q.all(all).then => + if @errors.length == 0 + StatusMessage.display('success', "All changes saved successfully") + form.$setPristine() if form? + else + StatusMessage.display('failure', "Oh no! I was unable to save your changes") all submit: (change) -> @@ -26,7 +36,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> @remove change.object.id, change.attr change.scope.reset( data["#{change.attr}"] ) change.scope.success() - , (error) -> + , (error) => + @errors.push error change.scope.error() changeCount: (objectChanges) -> diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index f905d33bfe..f105ebb0e5 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -5,27 +5,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.confirmDelete = true $scope.startDate = formatDate daysFromToday -7 $scope.endDate = formatDate daysFromToday 1 - $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ] + $scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ] $scope.selectedUnitsProduct = {} $scope.selectedUnitsVariant = {} $scope.sharedResource = false - $scope.columns = Columns.setColumns - order_no: { name: t("bom_no"), visible: false } - full_name: { name: t("name"), visible: true } - email: { name: t("email"), visible: false } - phone: { name: t("phone"), visible: false } - order_date: { name: t("bom_date"), visible: true } - producer: { name: t("producer"), visible: true } - order_cycle: { name: t("bom_cycle"), visible: false } - hub: { name: t("bom_hub"), visible: false } - variant: { name: t("bom_variant"), visible: true } - quantity: { name: t("bom_quantity"), visible: true } - max: { name: t("bom_max"), visible: true } - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } - price: { name: t("price"), visible: false } + $scope.columns = Columns.columns $scope.confirmRefresh = -> - LineItems.allSaved() || confirm(t "unsaved_changes_warning") + LineItems.allSaved() || confirm(t("unsaved_changes_warning")) $scope.resetSelectFilters = -> $scope.distributorFilter = blankOption().id diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee index 2c98d60f0e..3077aa8a11 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -12,7 +12,7 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded $scope.suppliedVariants = (enterprise_id) -> Enterprise.suppliedVariants(enterprise_id) @@ -79,5 +79,6 @@ angular.module('admin.orderCycles') $scope.removeDistributionOfVariant = (variant_id) -> OrderCycle.removeDistributionOfVariant(variant_id) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index fd426eb455..2f3b2db5e0 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -1,5 +1,5 @@ angular.module('admin.orderCycles') - .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> + .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) $scope.supplier_enterprises = Enterprise.producer_enterprises @@ -12,6 +12,9 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -60,6 +63,7 @@ angular.module('admin.orderCycles') $scope.removeExchange = ($event, exchange) -> $event.preventDefault() OrderCycle.removeExchange(exchange) + $scope.order_cycle_form.$dirty = true $scope.addCoordinatorFee = ($event) -> $event.preventDefault() @@ -81,4 +85,13 @@ angular.module('admin.orderCycles') OrderCycle.removeDistributionOfVariant(variant_id) $scope.submit = (destination) -> - OrderCycle.update(destination) + $event.preventDefault() + StatusMessage.display 'progress', "Saving..." + + $scope.submit = ($event, destination) -> + $event.preventDefault() + StatusMessage.display 'progress', "Saving..." + OrderCycle.update(destination, $scope.order_cycle_form) + + $scope.cancel = (destination) -> + $window.location = destination diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee index 53f00cdc36..dfa8011167 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -20,7 +20,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" OrderCycle.order_cycle.coordinator_id = enterprise.id $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded $scope.removeDistributionOfVariant = angular.noop @@ -41,6 +41,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" $scope.enterpriseFeesForEnterprise = (enterprise_id) -> EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index bf00bb3df1..f5bae5a4d8 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -9,6 +9,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) => $scope.init() + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -34,6 +37,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $event.preventDefault() OrderCycle.removeCoordinatorFee(index) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() + StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() - OrderCycle.update(destination) + OrderCycle.update(destination, $scope.order_cycle_form) diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 9ca5f1028d..662201c9f5 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -154,11 +154,12 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S else console.log('Failed to create order cycle') - update: (destination) -> + update: (destination, form) -> return unless @confirmNoDistributors() oc = new OrderCycleResource({order_cycle: this.dataForSubmit()}) oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) => if data['success'] + form.$setPristine() if form if destination? $window.location = destination else diff --git a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee index 6ad7f0bfb9..060b23bed9 100644 --- a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee +++ b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee @@ -2,13 +2,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr $scope.$compile = $compile $scope.shops = shops $scope.orderCycles = orderCycles - for oc in $scope.orderCycles - oc.name_and_status = "#{oc.name} (#{oc.status})" - $scope.distributor_id = $attrs.ofnDistributorId - $scope.order_cycle_id = $attrs.ofnOrderCycleId + $scope.distributor_id = parseInt($attrs.ofnDistributorId) + $scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId) - $scope.validOrderCycle = (oc, index, array) -> + $scope.validOrderCycle = (oc) -> $scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id) $scope.distributorHasOrderCycles = (distributor) -> @@ -20,3 +18,9 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr $scope.distributionChosen = -> $scope.distributor_id && $scope.order_cycle_id + + for oc in $scope.orderCycles + oc.name_and_status = "#{oc.name} (#{oc.status})" + + for shop in $scope.shops + shop.disabled = !$scope.distributorHasOrderCycles(shop) diff --git a/app/assets/javascripts/admin/orders/orders.js.coffee b/app/assets/javascripts/admin/orders/orders.js.coffee index abfe576095..4e1ec754e3 100644 --- a/app/assets/javascripts/admin/orders/orders.js.coffee +++ b/app/assets/javascripts/admin/orders/orders.js.coffee @@ -1 +1 @@ -angular.module("admin.orders", ['ngResource']) +angular.module("admin.orders", ['admin.indexUtils', 'ngResource']) diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee index cc7bd4ee3e..67c53d4b66 100644 --- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee @@ -1,2 +1,10 @@ -angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) -> +angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod, TagRuleResource, $q) -> $scope.shippingMethod = shippingMethod + + $scope.findTags = (query) -> + defer = $q.defer() + TagRuleResource.mapByTag (data) => + filtered = data.filter (tag) -> + tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 + defer.resolve filtered + defer.promise diff --git a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee index 863163a9ef..46537b303e 100644 --- a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee @@ -1 +1 @@ -angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates']) +angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'admin.tagRules', 'templates']) diff --git a/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee new file mode 100644 index 0000000000..d4a6c45ba4 --- /dev/null +++ b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee @@ -0,0 +1,10 @@ +angular.module("admin.tagRules").factory 'TagRuleResource', ($resource) -> + $resource('/admin/tag_rules/:action.json', {}, { + 'mapByTag': + method: 'GET' + isArray: true + cache: true + params: + action: 'map_by_tag' + enterprise_id: '@enterprise_id' + }) diff --git a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee index 88c7734c33..d9a4b2bbfd 100644 --- a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee +++ b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee @@ -1 +1 @@ -angular.module("admin.tagRules", ['ngTagsInput']) +angular.module("admin.tagRules", ['ngResource', 'ngTagsInput']) diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 13e4f84bc6..8bc6c98573 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -1,8 +1,9 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> restrict: "E" + transclude: true scope: - save: "&" - form: "=" + dirty: "=" + persist: "=?" templateUrl: "admin/save_bar.html" link: (scope, element, attrs) -> scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee index 26d1e05250..e72be8bf71 100644 --- a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -19,19 +19,10 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", hidden: { name: "Hidden Products", visible: false } new: { name: "New Products", visible: false } - $scope.columns = Columns.setColumns - producer: { name: "Producer", visible: true } - product: { name: "Product", visible: true } - sku: { name: "SKU", visible: false } - price: { name: "Price", visible: true } - on_hand: { name: "On Hand", visible: true } - on_demand: { name: "On Demand", visible: false } - reset: { name: "Reset Stock Level", visible: false } - inheritance: { name: "Inheritance", visible: false } - visibility: { name: "Hide", visible: false } - $scope.bulkActions = [ name: "Reset Stock Levels To Defaults", callback: 'resetStock' ] + $scope.columns = Columns.columns + $scope.resetSelectFilters = -> $scope.producerFilter = 0 $scope.query = '' diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee index c302c0463e..2af7ba1c16 100644 --- a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -1 +1 @@ -angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"]) +angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"]) diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index f4303f8037..600044f09c 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -11,8 +11,7 @@ #= require lodash.underscore.js #= require angular-scroll.min.js #= require angular-google-maps.min.js -#= require ../shared/mm-foundation-tpls-0.2.2.min.js -#= require ../shared/bindonce.min.js +#= require ../shared/mm-foundation-tpls-0.8.0.min.js #= require ../shared/ng-infinite-scroll.min.js #= require ../shared/angular-local-storage.js #= require ../shared/angular-slideables.js diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee index 123079b6b5..722414f9d4 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee @@ -1,5 +1,6 @@ Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) -> $scope.path = "/signup" + $scope.errors = email: null password: null diff --git a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee index 1a22f34b0c..135fe37b89 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee @@ -3,5 +3,10 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree $scope.toggle = AuthenticationService.toggle $scope.spree_user = SpreeUser.spree_user - $scope.active = AuthenticationService.active + $scope.isActive = AuthenticationService.isActive $scope.select = AuthenticationService.select + + $scope.tabs = + login: { active: $scope.isActive('/login') } + signup: { active: $scope.isActive('/signup') } + forgot: { active: $scope.isActive('/forgot') } diff --git a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee index ed52f46bb7..5d9bb0aa02 100644 --- a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter) -> +Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) -> $scope.Enterprises = Enterprises all_enterprises_by_id = Enterprises.enterprises_by_id @@ -19,4 +19,3 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M $scope.map = angular.copy MapConfiguration.options $scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises - diff --git a/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee new file mode 100644 index 0000000000..c9a8f88f58 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.controller "GroupTabsCtrl", ($scope, $controller, Navigation) -> + angular.extend this, $controller('TabsCtrl', {$scope: $scope}) + + $scope.tabs = + map: { active: Navigation.isActive('/map') } + about: { active: Navigation.isActive('/about') } + producers: { active: Navigation.isActive('/producers') } + hubs: { active: Navigation.isActive('/hubs') } diff --git a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee index aafb7597d6..8fd47c49f8 100644 --- a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee @@ -1,3 +1,3 @@ -Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) -> +Darkswarm.controller "GroupsCtrl", ($scope, Groups) -> $scope.Groups = Groups $scope.order = 'position' diff --git a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee index 07ddb4bc61..a692af3fb5 100644 --- a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee @@ -1,6 +1,7 @@ -Darkswarm.controller "ProductNodeCtrl", ($scope, $modal) -> +Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) -> $scope.enterprise = $scope.product.supplier # For the modal, so it's consistent $scope.triggerProductModal = -> + $scope.productTaxonSelectors = FilterSelectorsService.createSelectors() + $scope.productPropertySelectors = FilterSelectorsService.createSelectors() $modal.open(templateUrl: "product_modal.html", scope: $scope) - diff --git a/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee new file mode 100644 index 0000000000..8daac0212c --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) -> + angular.extend this, $controller('TabsCtrl', {$scope: $scope}) + + $scope.tabs = + about: { active: Navigation.isActive('/about') } + producers: { active: Navigation.isActive('/producers') } + contact: { active: Navigation.isActive('/contact') } + groups: { active: Navigation.isActive('/groups') } diff --git a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee index fad13164ff..09095ebd42 100644 --- a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee @@ -1,15 +1,6 @@ -Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location) -> - # Return active if supplied path matches url hash path. - $scope.active = (path)-> - $location.hash() == path +Darkswarm.controller "TabsCtrl", ($scope, Navigation) -> + $scope.isActive = Navigation.isActive # Select tab by setting the url hash path. - $scope.select = (path)-> - $location.hash path - - # Toggle tab selected status by setting the url hash path. - $scope.toggle = (path)-> - if $scope.active(path) - $location.hash "" - else - $location.hash path + $scope.select = (path) -> + Navigation.navigate path diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index a062ead058..ce5a73d69e 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,7 +1,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", 'mm.foundation', 'angularLocalStorage', - 'pasvaz.bindonce', 'infinite-scroll', 'angular-flash.service', 'templates', diff --git a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee index 10f75675f8..478f5f28e1 100644 --- a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee @@ -22,7 +22,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir @selectedPath = path Navigation.navigate @selectedPath - active: Navigation.active + isActive: Navigation.isActive close: -> if location.pathname in ["/", "/checkout"] diff --git a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee index a7d6a657fa..1434ffa44f 100644 --- a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee @@ -2,6 +2,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, new class EnterpriseRegistrationService enterprise: user_ids: [CurrentUser.id] + email: CurrentUser.email email_address: CurrentUser.email address: {} country: availableCountries[0] diff --git a/app/assets/javascripts/darkswarm/services/navigation.js.coffee b/app/assets/javascripts/darkswarm/services/navigation.js.coffee index fd59d0f348..e2b04f3ad1 100644 --- a/app/assets/javascripts/darkswarm/services/navigation.js.coffee +++ b/app/assets/javascripts/darkswarm/services/navigation.js.coffee @@ -1,9 +1,9 @@ Darkswarm.factory 'Navigation', ($location, $window) -> new class Navigation - path: null + path: null - active: (path)-> - $location.path() == path + isActive: (path)-> + $location.path() == path navigate: (path)=> @path = path diff --git a/app/assets/javascripts/shared/bindonce.min.js b/app/assets/javascripts/shared/bindonce.min.js deleted file mode 100644 index 0edd3d57d4..0000000000 --- a/app/assets/javascripts/shared/bindonce.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};return l}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}(); \ No newline at end of file diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js deleted file mode 100644 index be82613518..0000000000 --- a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * angular-mm-foundation - * http://madmimi.github.io/angular-foundation/ - - * Version: 0.2.2 - 2014-06-25 - * License: MIT - */ -angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.dropdownToggle","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(this.groups.indexOf(a),1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position"]).directive("dropdownToggle",["$document","$location","$position",function(a,b,c){var d=null,e=angular.noop;return{restrict:"CA",scope:{dropdownToggle:"@"},link:function(b,f){var g=angular.element(a[0].querySelector(b.dropdownToggle));b.$watch("$location.path",function(){e()}),f.bind("click",function(h){g=angular.element(a[0].querySelector(b.dropdownToggle));var i=f===d;if(h.preventDefault(),h.stopPropagation(),d&&e(),!i&&!f.hasClass("disabled")&&!f.prop("disabled")){g.css("display","block");var j=c.offset(f),k=c.offset(angular.element(g[0].offsetParent));g.css({left:j.left-k.left+"px",top:j.top-k.top+j.height+"px"}),d=f,e=function(){a.unbind("click",e),g.css("display","none"),e=angular.noop,d=null},a.bind("click",e)}}),g&&g.css("display","none")}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
")(l),f.append(k));var i=angular.element("
");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))},C=function(){};d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){a.selectExpression(a.$parent)},b.addTab=function(a){c.push(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.select&&(b.selectExpression=a(e.select)),e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||b.selectExpression(b.$parent)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){return parseInt(a.localStorage.getItem("mm.tour.step"),10)}function c(b){d=b,a.localStorage.setItem("mm.tour.step",b)}var d=b(),e={};this.add=function(a,b){e[a]=b},this.has=function(a){return!!e[a]},this.isActive=function(){return d>0},this.current=function(a){return a?void c(d):d},this.start=function(){c(1)},this.next=function(){c(d+1)},this.end=function(){c(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()}}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m){if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
\n {{heading}}\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
\n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","
\n \n ×\n
\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'
\n
\n
\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n') -}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
\n \n
\n

\n

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

\n

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

    \n

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

    \n

    \n Next\n End\n ×\n
    \n
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","
      \n"+'
    • \n
      \n
    • \n
    \n')}]); diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js index 9a1acd6e0d..9af98627db 100644 --- a/app/assets/javascripts/shared/ng-tags-input.min.js +++ b/app/assets/javascripts/shared/ng-tags-input.min.js @@ -1 +1 @@ -/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); \ No newline at end of file +/*! ngTagsInput v3.0.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",text:"=?",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&",onTagClicked:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tagList.items},getCurrentTagText:function(){return a.newTag.text()},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",l.items.length<=n.maxTags),i.$setValidity("minTags",l.items.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text())},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:function(a){return angular.isDefined(a)?(b.text=a,void m.trigger("input-change",a)):b.text||""},invalid:null},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){a?(l.items=h.makeObjectArray(a,n.displayProperty),b.tags=l.items):l.items=[]}),b.$watch("tags.length",function(){j(),i.$validate()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}},tag:{click:function(a){m.trigger("tag-clicked",{$tag:a})}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-clicked",b.onTagClicked).on("tag-added",function(){b.newTag.text("")}).on("tag-added tag-removed",function(){b.tags=l.items,i.$setDirty()}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text()),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,i=c.keyCode,j={};if(!h.isModifierOn(c)&&-1!==k.indexOf(i)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[i],e=(i===a.backspace||i===a["delete"])&&l.selected,g=i===a.backspace&&0===b.newTag.text().length&&n.enableEditingLastTag,f=(i===a.backspace||i===a.left||i===a.right)&&0===b.newTag.text().length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text());else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.text(m[n.displayProperty])}else e?l.removeSelected():f&&(i===a.left||i===a.backspace?l.selectPrior():i===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,h=[a.enter,a.tab,a.escape,a.up,a.down],j=b.suggestionList,k=e.registerAutocomplete(),l=b.options,m=b.events;l.tagsInput=k.getOptions(),f=function(a){return a&&a.length>=l.minLength||!a&&l.loadOnEmpty},b.addSuggestionByIndex=function(a){j.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return j.selected&&(k.addTag(angular.copy(j.selected)),j.reset(),k.focusInput(),a=!0),a},b.track=function(a){return a[l.tagsInput.keyProperty||l.tagsInput.displayProperty]},k.on("tag-added tag-removed invalid-tag input-blur",function(){j.reset()}).on("input-change",function(a){f(a)?j.load(a,k.getTags()):j.reset()}).on("input-focus",function(){var a=k.getCurrentTagText();l.loadOnFocus&&f(a)&&j.load(a,k.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(!g.isModifierOn(c)&&-1!==h.indexOf(d))return j.visible?d===a.down?(j.selectNext(),e=!0):d===a.up?(j.selectPrior(),e=!0):d===a.escape?(j.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(j.load(k.getCurrentTagText(),k.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),m.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){if(!angular.isArray(a)||0===a.length||angular.isObject(a[0]))return a;var c=[];return a.forEach(function(a){var d={};d[b]=a,c.push(d)}),c},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.isModifierOn=function(a){return a.shiftKey||a.ctrlKey||a.altKey||a.metaKey},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); diff --git a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml new file mode 100644 index 0000000000..37398b200a --- /dev/null +++ b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml @@ -0,0 +1,11 @@ +.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } } + %span{ :class => 'icon-reorder' }= "  #{t('admin.columns')}".html_safe + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } } + %span.check + %span.name {{column.name }} + %hr + %div.menu_item.text-center + %input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } } + %input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} } diff --git a/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml new file mode 100644 index 0000000000..e30cfcd607 --- /dev/null +++ b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml @@ -0,0 +1,15 @@ +#new-customer-dialog + .text-normal.margin-bottom-30.text-center + = t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:") + + %form{ name: 'new_customer_form', novalidate: true } + + .text-center.margin-bottom-30 + %input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } } + %div{ ng: { show: "email == submitted" } } + .error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } } + = t('admin.customers.index.valid_email_error') + .error{ ng: { repeat: "error in errors", bind: "error" } } + + .text-center + %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } } diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml index 39392a497d..518122774d 100644 --- a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml +++ b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml @@ -17,13 +17,13 @@ %td.severity %i.icon-warning-sign.issue %td.description - %span{ bo: { bind: "issue.description" } } + %span{ ng: { bind: "::issue.description" } } %td.resolve %div{ ng: { bind: { html: "issue.link" } } } %tr{ ng: { repeat: "warning in warnings"} } %td.severity %i.icon-warning-sign.warning %td.description - %span{ bo: { bind: "warning.description" } } + %span{ ng: { bind: "::warning.description" } } %td.resolve %div{ ng: { bind: { html: "warning.link" } } } diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 452e81f6e3..12ccd4537c 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,6 +1,6 @@ -#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } - .twelve.columns.alpha - %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } - {{ StatusMessage.statusMessage.text || " " }} - .four.columns.omega.text-right - %input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } } +#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } } + .container + .eight.columns.alpha + %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } + {{ StatusMessage.statusMessage.text || " " }} + .eight.columns.omega.text-right{ ng: { transclude: true } } diff --git a/app/assets/javascripts/templates/admin/show_more.html.haml b/app/assets/javascripts/templates/admin/show_more.html.haml new file mode 100644 index 0000000000..f9a008993c --- /dev/null +++ b/app/assets/javascripts/templates/admin/show_more.html.haml @@ -0,0 +1,4 @@ +%div{ ng: { show: "data.length > limit" } } + %input{ type: 'button', value: 'Show More', ng: { click: 'limit = limit + increment' } } + or + %input{ type: 'button', value: "Show All ({{ data.length - limit }} More)", ng: { click: 'limit = data.length' } } diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml index a53c4f44db..cd4135ebee 100644 --- a/app/assets/javascripts/templates/filter_selector.html.haml +++ b/app/assets/javascripts/templates/filter_selector.html.haml @@ -1,4 +1,4 @@ -%ul{ bindonce: true } +%ul %active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } } %render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} } - %span{"bo-text" => "selector.object.name"} + %span{"ng-bind" => "::selector.object.name"} diff --git a/app/assets/javascripts/templates/forgot.html.haml b/app/assets/javascripts/templates/forgot.html.haml index c1990d778a..958f1daa39 100644 --- a/app/assets/javascripts/templates/forgot.html.haml +++ b/app/assets/javascripts/templates/forgot.html.haml @@ -1,10 +1,5 @@ -%tab#forgot{"ng-controller" => "ForgotCtrl", - heading: "{{'forgot_password' | t}}", - active: "active(path)", - select: "select(path)"} - - %form{"ng-submit" => "submit()"} - +%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"} + %form{ ng: { controller: "ForgotCtrl", submit: "submit()" } } .row .large-12.columns .alert-box.success.radius{"ng-show" => "sent"} @@ -13,18 +8,18 @@ %div{"ng-show" => "!sent"} .alert-box.alert{"ng-show" => "errors != null"} {{ errors }} - + .row .large-12.columns %label{for: "email"} {{'signup_email' | t}} - %input.title.input-text{name: "email", + %input.title.input-text{name: "email", type: "email", id: "email", tabindex: 1, "ng-model" => "spree_user.email"} .row .large-12.columns - %input.button.primary{name: "commit", - tabindex: "3", - type: "submit", + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", value: "{{'reset_password' | t}}"} diff --git a/app/assets/javascripts/templates/login.html.haml b/app/assets/javascripts/templates/login.html.haml index 44d2e08ced..79511d8968 100644 --- a/app/assets/javascripts/templates/login.html.haml +++ b/app/assets/javascripts/templates/login.html.haml @@ -1,8 +1,5 @@ -%tab#login-content{"ng-controller" => "LoginCtrl", - heading: "{{'label_login' | t}}", - active: "active(path)", - select: "select(path)"} - %form{"ng-submit" => "submit()"} +%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"} + %form{ ng: { controller: "LoginCtrl", submit: "submit()" } } .row .large-12.columns .alert-box.alert{"ng-show" => "errors != null"} @@ -10,7 +7,7 @@ .row .large-12.columns %label{for: "email"} {{'email' | t}} - %input.title.input-text{name: "email", + %input.title.input-text{name: "email", type: "email", id: "email", tabindex: 1, @@ -18,7 +15,7 @@ .row .large-12.columns %label{for: "password"} {{'password' | t}} - %input.title.input-text{name: "password", + %input.title.input-text{name: "password", type: "password", id: "password", autocomplete: "off", @@ -26,15 +23,15 @@ "ng-model" => "spree_user.password"} .row .large-12.columns - %input{name: "remember_me", - type: "checkbox", - id: "remember_me", + %input{name: "remember_me", + type: "checkbox", + id: "remember_me", value: "1", "ng-model" => "spree_user.remember_me"} %label{for: "remember_me"} {{'remember_me' | t}} .row .large-12.columns - %input.button.primary{name: "commit", - tabindex: "3", - type: "submit", + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", value: "{{'label_login' | t}}"} diff --git a/app/assets/javascripts/templates/partials/contact.html.haml b/app/assets/javascripts/templates/partials/contact.html.haml index 12aa5ff061..d173bcf366 100644 --- a/app/assets/javascripts/templates/partials/contact.html.haml +++ b/app/assets/javascripts/templates/partials/contact.html.haml @@ -1,11 +1,11 @@ -%div.contact-container{bindonce: true} - %div.modal-centered{"bo-if" => "enterprise.email_address || enterprise.website || enterprise.phone"} +%div.contact-container + %div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone"} %p.modal-header {{'contact' | t}} - %p{"bo-if" => "enterprise.phone", "bo-text" => "enterprise.phone"} + %p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"} - %p.word-wrap{"ng-if" => "enterprise.email_address"} - %a{"bo-href" => "enterprise.email_address | stripUrl", target: "_blank", mailto: true} - %span.email{"bo-bind" => "enterprise.email_address | stripUrl"} + %p.word-wrap{"ng-if" => "::enterprise.email_address"} + %a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true} + %span.email{"ng-bind" => "::enterprise.email_address | stripUrl"} %p.word-wrap{"ng-if" => "enterprise.website"} - %a{"bo-href-i" => "http://{{enterprise.website | stripUrl}}", target: "_blank", "bo-bind" => "enterprise.website | stripUrl"} + %a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"} diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml index fc0a638c8d..d1d208c5c4 100644 --- a/app/assets/javascripts/templates/partials/enterprise_details.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml @@ -1,4 +1,4 @@ -.row{bindonce: true} +.row .small-12.large-8.columns / TODO: Rob add logic for taxons and properties too: / %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"} @@ -22,8 +22,8 @@ -# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/ -# .about-container.pad-top - %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"} - %p.text-small{"ng-bind-html" => "enterprise.long_description"} + %img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"} + %p.text-small{"ng-bind-html" => "::enterprise.long_description"} .small-12.large-4.columns %ng-include{src: "'partials/contact.html'"} %ng-include{src: "'partials/follow.html'"} diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml index 44175a57fe..066ebdd957 100644 --- a/app/assets/javascripts/templates/partials/enterprise_header.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml @@ -1,13 +1,13 @@ -.highlight{bindonce: true, "ng-class" => "{'is_distributor' : enterprise.is_distributor}"} +.highlight{"ng-class" => "::{'is_distributor' : enterprise.is_distributor}"} .highlight-top.row .small-12.medium-7.large-8.columns - %h3{"ng-if" => "enterprise.is_distributor"} - %a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"} - %i{"ng-class" => "enterprise.icon_font"} - %span{"bo-text" => "enterprise.name"} - %h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"} - %i{"ng-class" => "enterprise.icon_font"} - %span{"bo-text" => "enterprise.name"} + %h3{"ng-if" => "::enterprise.is_distributor"} + %a{"ng-href" => "{{::enterprise.path}}", "ofn-change-hub" => "enterprise"} + %i{"ng-class" => "::enterprise.icon_font"} + %span{"ng-bind" => "::enterprise.name"} + %h3{"ng-if" => "::!enterprise.is_distributor", "ng-class" => "::{'is_producer' : enterprise.is_primary_producer}"} + %i{"ng-class" => "::enterprise.icon_font"} + %span{"ng-bind" => "::enterprise.name"} .small-12.medium-5.large-4.columns.text-right.small-only-text-left - %p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"} - %img.hero-img{"bo-src" => "enterprise.promo_image"} + %p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"} + %img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"} diff --git a/app/assets/javascripts/templates/partials/follow.html.haml b/app/assets/javascripts/templates/partials/follow.html.haml index c04567a357..a47054d1f3 100644 --- a/app/assets/javascripts/templates/partials/follow.html.haml +++ b/app/assets/javascripts/templates/partials/follow.html.haml @@ -1,19 +1,18 @@ -%div.modal-centered{bindonce: true, "bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"} +%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"} %p.modal-header {{'follow' | t}} .follow-icons - %span{"bo-if" => "enterprise.twitter"} - %a{"bo-href-i" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"} + %span{"ng-if" => "::enterprise.twitter"} + %a{"ng-href" => "http://twitter.com/{{::enterprise.twitter}}", target: "_blank"} %i.ofn-i_041-twitter - - %span{"bo-if" => "enterprise.facebook"} - %a{"bo-href-i" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"} - %i.ofn-i_044-facebook - - %span{"bo-if" => "enterprise.linkedin"} - %a{"bo-href-i" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"} - %i.ofn-i_042-linkedin - - %span{"bo-if" => "enterprise.instagram"} - %a{"bo-href-i" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"} - %i.ofn-i_043-instagram + %span{"ng-if" => "::enterprise.facebook"} + %a{"ng-href" => "http://{{::enterprise.facebook | stripUrl}}", target: "_blank"} + %i.ofn-i_044-facebook + + %span{"ng-if" => "::enterprise.linkedin"} + %a{"ng-href" => "http://{{::enterprise.linkedin | stripUrl}}", target: "_blank"} + %i.ofn-i_042-linkedin + + %span{"ng-if" => "::enterprise.instagram"} + %a{"ng-href" => "http://instagram.com/{{::enterprise.instagram}}", target: "_blank"} + %i.ofn-i_043-instagram diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index fa75f72253..e3a0234df1 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -1,24 +1,24 @@ -.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } } +.row.pad-top{ng: { if: 'enterprise.is_distributor' } } .cta-container.small-12.columns .row .small-4.columns %label{"active-table-hub-link" => "enterprise", change: "{{'change_shop' | t}}", shop: "{{'shop_at' | t}}"} .small-8.columns.right - %label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"} + %label.right{"ng-if" => "::enterprise.pickup || enterprise.delivery"} {{'hubs_delivery_options' | t}}: - %span{"bo-if" => "enterprise.pickup"} + %span{"ng-if" => "::enterprise.pickup"} %i.ofn-i_038-takeaway {{'hubs_pickup' | t}} - %span{"bo-if" => "enterprise.delivery"} + %span{"ng-if" => "::enterprise.delivery"} %i.ofn-i_039-delivery {{'hubs_delivery' | t}} .row .columns.small-12 - %a.cta-hub{"bo-href" => "enterprise.path", + %a.cta-hub{"ng-href" => "{{::enterprise.path}}", "ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}", "ofn-change-hub" => "enterprise"} - %i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"} - .hub-name{"bo-text" => "enterprise.name"} - .button-address{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"} + %i.ofn-i_033-open-sign{"ng-if" => "::enterprise.active"} + %i.ofn-i_032-closed-sign{"ng-if" => "::!enterprise.active"} + .hub-name{"ng-bind" => "::enterprise.name"} + .button-address{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"} / %i.ofn-i_007-caret-right diff --git a/app/assets/javascripts/templates/partials/producer_details.html.haml b/app/assets/javascripts/templates/partials/producer_details.html.haml index 599b977dfc..cb7ee70482 100644 --- a/app/assets/javascripts/templates/partials/producer_details.html.haml +++ b/app/assets/javascripts/templates/partials/producer_details.html.haml @@ -1,20 +1,20 @@ -# Show places to buy products from this producer, when there are any -# Do not show this for producer shops selling only their own produce, -# Since a shopping link will already have been displayed in hub_details.html.haml -.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"} +.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"} .columns.small-12 .row .columns.small-12.fat - %div{"bo-if" => "enterprise.name"} - %label{"bo-html" => "'shop_for_products_html' | t:{enterprise: enterprise.name}"} - %div.show-for-medium-up{"bo-if" => "!enterprise.name"} + %div{"ng-if" => "::enterprise.name"} + %label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"} + %div.show-for-medium-up{"ng-if" => "::!enterprise.name"}   .row.cta-container .columns.small-12 %a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'", - "bo-href" => "hub.path", "ofn-empties-cart" => "hub", - "bo-class" => "{primary: hub.active, secondary: !hub.active}"} - %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - .hub-name{"bo-text" => "hub.name"} - .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"} + "ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}"} + %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"} + %i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"} + .hub-name{"ng-bind" => "::hub.name"} + .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"} diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml index 1153d93b0f..3a52ba9d07 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -1,4 +1,4 @@ -.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"} +.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"} %input{type: :number, integer: true, diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index 6601f0c019..d4c88092b6 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -1,4 +1,4 @@ -.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"} +.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"} %span.bulk-input-container %span.bulk-input %input.bulk.first{type: :number, diff --git a/app/assets/javascripts/templates/price_breakdown.html.haml b/app/assets/javascripts/templates/price_breakdown.html.haml index 993c20bc9b..fa884cd4f0 100644 --- a/app/assets/javascripts/templates/price_breakdown.html.haml +++ b/app/assets/javascripts/templates/price_breakdown.html.haml @@ -1,38 +1,37 @@ -.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"} +.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"} %span.joyride-nub.right .joyride-content-wrapper .collapsed{"ng-show" => "!expanded"} %price-percentage{percentage: 'variant.basePricePercentage'} - %a{"ng-click" => "expanded = !expanded"} - %span{"bo-text" => "'price_breakdown' | t"} + %a{"ng-click" => "expanded = !expanded"} + %span{"ng-bind" => "::'price_breakdown' | t"} %i.ofn-i_005-caret-down .expanded{"ng-show" => "expanded"} %ul %li.cost .right {{ variant.price | localizeCurrency }} - %span{"bo-text" => "'item_cost' | t"} - %li.admin-fee{"bo-if" => "variant.fees.admin"} + %span{"ng-bind" => "::'item_cost' | t"} + %li.admin-fee{"ng-if" => "::variant.fees.admin"} .right {{ variant.fees.admin | localizeCurrency }} - %span{"bo-text" => "'admin_fee' | t"} - %li.sales-fee{"bo-if" => "variant.fees.sales"} + %span{"ng-bind" => "::'admin_fee' | t"} + %li.sales-fee{"ng-if" => "::variant.fees.sales"} .right {{ variant.fees.sales | localizeCurrency }} - %span{"bo-text" => "'sales_fee' | t"} - %li.packing-fee{"bo-if" => "variant.fees.packing"} + %span{"ng-bind" => "::'sales_fee' | t"} + %li.packing-fee{"ng-if" => "::variant.fees.packing"} .right {{ variant.fees.packing | localizeCurrency }} - %span{"bo-text" => "'packing_fee' | t"} - %li.transport-fee{"bo-if" => "variant.fees.transport"} + %span{"ng-bind" => "::'packing_fee' | t"} + %li.transport-fee{"ng-if" => "::variant.fees.transport"} .right {{ variant.fees.transport | localizeCurrency }} - %span{"bo-text" => "'transport_fee' | t"} - %li.fundraising-fee{"bo-if" => "variant.fees.fundraising"} + %span{"ng-bind" => "::'transport_fee' | t"} + %li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"} .right {{ variant.fees.fundraising | localizeCurrency }} - %span{"bo-text" => "'fundraising_fee' | t"} + %span{"ng-bind" => "::'fundraising_fee' | t"} %li.total %strong .right = {{ variant.price_with_fees | localizeCurrency }}   - %a{"ng-click" => "expanded = !expanded"} - %span{"bo-text" => "'price_graph' | t"} + %a{"ng-click" => "expanded = !expanded"} + %span{"ng-bind" => "::'price_graph' | t"} %i.ofn-i_006-caret-up - diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index e13f6df60d..4b21c054a4 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -1,26 +1,26 @@ -.row{bindonce: true} +.row .columns.small-12.large-6.product-header - %h3{"bo-text" => "product.name"} + %h3{"ng-bind" => "::product.name"} %span %em {{'products_from' | t}} - %span{"bo-text" => "enterprise.name"} + %span{"ng-bind" => "::enterprise.name"} %br .filter-shopfront.taxon-selectors.inline-block - %filter-selector{ objects: "[product] | taxonsOf" } + %filter-selector{ 'selector-set' => "productTaxonSelectors", objects: "[product] | taxonsOf" } .filter-shopfront.property-selectors.inline-block - %filter-selector{ objects: "[product] | propertiesWithValuesOf" } + %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } %div{"ng-if" => "product.description"} %hr - %p.text-small{"bo-text" => "product.description"} + %p.text-small{"ng-bind" => "::product.description"} %hr .columns.small-12.large-6 - %img.product-img{"bo-src" => "product.largeImage", "bo-if" => "product.largeImage"} - %img.product-img.placeholder{"bo-src" => "'/assets/noimage/large.png'", "bo-if" => "!product.largeImage"} + %img.product-img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"} + %img.product-img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"} %ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/registration/details.html.haml b/app/assets/javascripts/templates/registration/details.html.haml index f03a48ec59..6bd3fc02ba 100644 --- a/app/assets/javascripts/templates/registration/details.html.haml +++ b/app/assets/javascripts/templates/registration/details.html.haml @@ -1,19 +1,19 @@ -.container#registration-details{bindonce: true} +.container#registration-details %ng-include{ src: "'registration/steps.html'" } .row .small-12.columns %header %h2 {{'registration_detail_headline' | t}} - %h5{ bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}} - %h5{ bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_producer' | t}} + %h5{ ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}} + %h5{ ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_producer' | t}} %form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } } .row .small-12.medium-9.large-12.columns.end .field - %label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}} - %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}} + %label{ for: 'enterprise_name', ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}} + %label{ for: 'enterprise_name', ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}} %input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "{{'registration_detail_name_placeholder' | t}}", required: true, ng: { model: 'enterprise.name' } } %span.error{ ng: { show: "details.name.$error.required && submitted" } } {{'registration_detail_name_error' | t}} diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml index 5c7b3b6ad0..76403cb1c8 100644 --- a/app/assets/javascripts/templates/registration/finished.html.haml +++ b/app/assets/javascripts/templates/registration/finished.html.haml @@ -10,6 +10,6 @@ .small-12.columns.text-center %h4{ "ng-bind" => "'registration_finished_activate' | t:{enterprise: enterprise.name}" } - %p{ "ng-bind-html" => "t('registration_finished_activate_instruction_html', {email: enterprise.email})"} + %p{ "ng-bind-html" => "'registration_finished_activate_instruction_html' | t:{email: enterprise.email}"} %a.button.primary{ type: "button", href: "/" } {{'registration_finished_action' | t}} > diff --git a/app/assets/javascripts/templates/registration/type.html.haml b/app/assets/javascripts/templates/registration/type.html.haml index c63b40239e..85a81398d7 100644 --- a/app/assets/javascripts/templates/registration/type.html.haml +++ b/app/assets/javascripts/templates/registration/type.html.haml @@ -1,4 +1,4 @@ -.container#registration-type{bindonce: true} +.container#registration-type %ng-include{ src: "'registration/steps.html'" } @@ -10,7 +10,7 @@ {{'registration_type_question' | t}} %form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } } - .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } } + .row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } } .small-12.columns.field .row .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index 2c9eb932d5..1d06ffbb39 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -2,16 +2,14 @@ .small-12.medium-4.large-4.columns.variant-name .table-cell .inline {{ variant.name_to_display }} - .bulk-buy.inline{"bo-if" => "variant.product.group_buy"} + .bulk-buy.inline{"ng-if" => "::variant.product.group_buy"} %i.ofn-i_056-bulk>< %em>< \ {{'bulk' | t}} - %ng-include{src: "'partials/shop_variant_no_group_buy.html'"} %ng-include{src: "'partials/shop_variant_with_group_buy.html'"} - .small-3.medium-1.large-1.columns.variant-unit .table-cell %em {{ variant.unit_to_display }} diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index 2e780aabb5..23eab8e38c 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -1,8 +1,5 @@ -%tab#sign-up-content{"ng-controller" => "SignupCtrl", - heading: "{{'label_signup' | t}}", - active: "active(path)", - select: "select(path)"} - %form{"ng-submit" => "submit()"} +%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"} + %form{ ng: { controller: "SignupCtrl", submit: "submit()" } } .row .large-12.columns %label{for: "email"} {{'signup_email' | t}} diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.css index e0d668b95b..e913e9e710 100644 --- a/app/assets/stylesheets/admin/all.css +++ b/app/assets/stylesheets/admin/all.css @@ -9,7 +9,7 @@ *= require admin/spree_promo *= require shared/jquery-ui-timepicker-addon - *= require shared/textAngular.min + *= require shared/textAngular *= require shared/ng-tags-input.min *= require_self diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index c6b1236490..f453c5ddfc 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -1,9 +1,14 @@ #save-bar position: fixed + width: 100% + z-index: 100 bottom: 0px - padding: 8px 10px + left: 0 + padding: 8px 8px font-weight: bold - background-color: #fff + background-color: #eff5fc color: #5498da h5 color: #5498da + input + margin-right: 5px diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 17d43ff3a5..8d77015af7 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -28,6 +28,9 @@ text-angular .ta-editor { left: 275px; } +span.error, div.error { + color: #DA5354; +} /* Fix conflict between Spree and elRTE's styles */ .el-rte .toolbar { @@ -37,6 +40,12 @@ text-angular .ta-editor { input.red { background-color: #DA5354; + margin-right: 5px; +} + +input.orange { + background-color: #FF9848; + margin-right: 5px; } input.search { diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 761ccbc014..23cb9d8fff 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -13,10 +13,6 @@ input.show-dirty { } } -span.error { - color: #DA5354; -} - input, div { &.update-error { border: solid 1px #DA5354; diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass index eab074369e..5cfcef46b8 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -84,16 +84,25 @@ padding-right: 0rem font-size: 0.8rem - .shopfront_message, .shopfront_closed_message, .shopfront_hidden_message + .alert-box.shopfront-message + border: 2px solid $clr-turquoise + border-radius: 5px + background-color: $clr-turquoise-light + color: $clr-turquoise + a + color: #0096ad + &:hover, &:focus, &:active + text-decoration: none + color: #4aadbd + + + .shopfront_closed_message, .shopfront_hidden_message padding: 15px border-radius: 5px - .shopfront_message, .shopfront_closed_message + .shopfront_closed_message border: 2px solid #eb4c46 - .shopfront_message - margin-top: 2em - .shopfront_closed_message margin: 2em 0em diff --git a/app/assets/stylesheets/mail/email.css.sass b/app/assets/stylesheets/mail/email.css.sass index 4256357923..e9eabd4edb 100644 --- a/app/assets/stylesheets/mail/email.css.sass +++ b/app/assets/stylesheets/mail/email.css.sass @@ -74,6 +74,9 @@ table.order-summary padding-left: 5px padding-right: 5px +.text-right + text-align: right + .social .soc-btn padding: 3px 7px font-size: 12px diff --git a/app/assets/stylesheets/shared/ng-tags-input.min.css b/app/assets/stylesheets/shared/ng-tags-input.min.css index ee4a4a98d5..22dcf47d66 100755 --- a/app/assets/stylesheets/shared/ng-tags-input.min.css +++ b/app/assets/stylesheets/shared/ng-tags-input.min.css @@ -1 +1 @@ -tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff} \ No newline at end of file +tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .input.invalid-tag,tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0 0 0 5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff} diff --git a/app/assets/stylesheets/shared/textAngular.css b/app/assets/stylesheets/shared/textAngular.css new file mode 100644 index 0000000000..a2f76234dc --- /dev/null +++ b/app/assets/stylesheets/shared/textAngular.css @@ -0,0 +1,193 @@ +.ta-hidden-input { + width: 1px; + height: 1px; + border: none; + margin: 0; + padding: 0; + position: absolute; + top: -10000px; + left: -10000px; + opacity: 0; + overflow: hidden; +} + +/* add generic styling for the editor */ +.ta-root.focussed > .ta-scroll-window.form-control { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.ta-editor.ta-html, .ta-scroll-window.form-control { + min-height: 300px; + height: auto; + overflow: auto; + font-family: inherit; + font-size: 100%; +} + +.ta-scroll-window.form-control { + position: relative; + padding: 0; +} + +.ta-scroll-window > .ta-bind { + height: auto; + min-height: 300px; + padding: 6px 12px; +} + +.ta-editor:focus { + user-select: text; +} + +/* add the styling for the awesomness of the resizer */ +.ta-resizer-handle-overlay { + z-index: 100; + position: absolute; + display: none; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-info { + position: absolute; + bottom: 16px; + right: 16px; + border: 1px solid black; + background-color: #FFF; + padding: 0 4px; + opacity: 0.7; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-background { + position: absolute; + bottom: 5px; + right: 5px; + left: 5px; + top: 5px; + border: 1px solid black; + background-color: rgba(0, 0, 0, 0.2); +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner { + width: 10px; + height: 10px; + position: absolute; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tl{ + top: 0; + left: 0; + border-left: 1px solid black; + border-top: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tr{ + top: 0; + right: 0; + border-right: 1px solid black; + border-top: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-bl{ + bottom: 0; + left: 0; + border-left: 1px solid black; + border-bottom: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-br{ + bottom: 0; + right: 0; + border: 1px solid black; + cursor: se-resize; + background-color: white; +} + +/* copy the popover code from bootstrap so this will work even without it */ +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} diff --git a/app/assets/stylesheets/shared/textAngular.min.css b/app/assets/stylesheets/shared/textAngular.min.css deleted file mode 100644 index 6f132953bb..0000000000 --- a/app/assets/stylesheets/shared/textAngular.min.css +++ /dev/null @@ -1 +0,0 @@ -.ta-scroll-window.form-control{height:auto;min-height:300px;overflow:auto;font-family:inherit;font-size:100%;position:relative;padding:0}.ta-root.focussed .ta-scroll-window.form-control{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.ta-editor.ta-html{min-height:300px;height:auto;overflow:auto;font-family:inherit;font-size:100%}.ta-scroll-window>.ta-bind{height:auto;min-height:300px;padding:6px 12px}.ta-root .ta-resizer-handle-overlay{z-index:100;position:absolute;display:none}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-info{position:absolute;bottom:16px;right:16px;border:1px solid #000;background-color:#FFF;padding:0 4px;opacity:.7}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-background{position:absolute;bottom:5px;right:5px;left:5px;top:5px;border:1px solid #000;background-color:rgba(0,0,0,.2)}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner{width:10px;height:10px;position:absolute}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tl{top:0;left:0;border-left:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tr{top:0;right:0;border-right:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-bl{bottom:0;left:0;border-left:1px solid #000;border-bottom:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-br{bottom:0;right:0;border:1px solid #000;cursor:se-resize;background-color:#fff} \ No newline at end of file diff --git a/app/controllers/admin/business_model_configuration_controller.rb b/app/controllers/admin/business_model_configuration_controller.rb index 312a2e3208..b3c3c67ab8 100644 --- a/app/controllers/admin/business_model_configuration_controller.rb +++ b/app/controllers/admin/business_model_configuration_controller.rb @@ -18,10 +18,13 @@ class Admin::BusinessModelConfigurationController < Spree::Admin::BaseController def load_settings @settings = OpenFoodNetwork::BusinessModelConfigurationValidator.new(params[:settings] || { + shop_trial_length_days: Spree::Config[:shop_trial_length_days], account_invoices_monthly_fixed: Spree::Config[:account_invoices_monthly_fixed], account_invoices_monthly_rate: Spree::Config[:account_invoices_monthly_rate], account_invoices_monthly_cap: Spree::Config[:account_invoices_monthly_cap], - account_invoices_tax_rate: Spree::Config[:account_invoices_tax_rate] + account_invoices_tax_rate: Spree::Config[:account_invoices_tax_rate], + minimum_billable_turnover: Spree::Config[:minimum_billable_turnover] + }) end diff --git a/app/controllers/admin/column_preferences_controller.rb b/app/controllers/admin/column_preferences_controller.rb new file mode 100644 index 0000000000..0a9300d71c --- /dev/null +++ b/app/controllers/admin/column_preferences_controller.rb @@ -0,0 +1,38 @@ +module Admin + class ColumnPreferencesController < ResourceController + before_filter :load_collection, only: [:bulk_update] + + respond_to :json + + def bulk_update + @cp_set.collection.each { |cp| authorize! :bulk_update, cp } + + if @cp_set.save + # Return saved VOs with IDs + render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer + else + if @cp_set.errors.present? + render json: { errors: @cp_set.errors }, status: 400 + else + render nothing: true, status: 500 + end + end + end + + private + + def load_collection + collection_hash = Hash[params[:column_preferences].each_with_index.map { |cp, i| [i, cp] }] + collection_hash.reject!{ |i, cp| cp[:action_name] != params[:action_name] } + @cp_set = ColumnPreferenceSet.new @column_preferences, collection_attributes: collection_hash + end + + def collection + ColumnPreference.where(user_id: spree_current_user, action_name: params[:action_name]) + end + + def collection_actions + [:bulk_update] + end + end +end diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index 24c26661e5..fe975aa038 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -7,11 +7,8 @@ module Admin respond_to do |format| format.html format.json do - serialised = ActiveModel::ArraySerializer.new( - @collection, - each_serializer: Api::Admin::CustomerSerializer, - spree_current_user: spree_current_user) - render json: serialised.to_json + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id])) + render_as_json @collection, tag_rule_mapping: tag_rule_mapping end end end @@ -19,8 +16,12 @@ module Admin def create @customer = Customer.new(params[:customer]) if user_can_create_customer? - @customer.save - render json: Api::Admin::CustomerSerializer.new(@customer).to_json + if @customer.save + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise)) + render_as_json @customer, tag_rule_mapping: tag_rule_mapping + else + render json: { errors: @customer.errors.full_messages }, status: 400 + end else redirect_to '/unauthorized' end diff --git a/app/controllers/admin/tag_rules_controller.rb b/app/controllers/admin/tag_rules_controller.rb index 7d60cb4888..25f5b177d6 100644 --- a/app/controllers/admin/tag_rules_controller.rb +++ b/app/controllers/admin/tag_rules_controller.rb @@ -6,5 +6,38 @@ module Admin respond_override destroy: { json: { success: lambda { render nothing: true, :status => 204 } } } + + def map_by_tag + respond_to do |format| + format.json do + serialiser = ActiveModel::ArraySerializer.new(collection) + render json: serialiser.to_json + end + end + end + + + private + + def collection_actions + [:index, :map_by_tag] + end + + def collection + case action + when :map_by_tag + TagRule.mapping_for(enterprises).values + else + TagRule.for(enterprises.pluck(&:id)) + end + end + + def enterprises + if params[:enterprise_id] + Enterprise.managed_by(spree_current_user).where(id: params[:enterprise_id]) + else + Enterprise.managed_by(spree_current_user) + end + end end end diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb deleted file mode 100644 index 3ab5685ffe..0000000000 --- a/app/controllers/admin/tags_controller.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Admin - class TagsController < Spree::Admin::BaseController - respond_to :json - - def index - respond_to do |format| - format.json do - serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise) - render json: serialiser.to_json - end - end - end - - private - - def enterprise - Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id]) - end - - def tags_of_enterprise - return [] unless enterprise - tag_rule_map = enterprise.rules_per_tag - tag_rule_map.keys.map do |tag| - { text: tag, rules: tag_rule_map[tag] } - end - end - end -end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 0c032eaa9e..079f93d41f 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -47,13 +47,19 @@ module Admin admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer end + def admin_inject_column_preferences(opts={}) + opts.reverse_merge!(module: 'ofn.admin', action: "#{controller_name}_#{action_name}") + column_preferences = ColumnPreference.for(spree_current_user, opts[:action]) + admin_inject_json_ams_array opts[:module], "columns", column_preferences, Api::Admin::ColumnPreferenceSerializer + end + def admin_inject_enterprise_permissions permissions = {can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise), can_manage_payment_methods: can?(:manage_payment_methods, @enterprise), can_manage_enterprise_fees: can?(:manage_enterprise_fees, @enterprise)} - render partial: "admin/json/injection_ams", locals: {ngModule: "admin.enterprises", name: "enterprisePermissions", json: permissions.to_json} + admin_inject_json "admin.enterprises", "enterprisePermissions", permissions end def admin_inject_hub_permissions @@ -96,6 +102,11 @@ module Admin render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end + def admin_inject_json(ngModule, name, data) + json = data.to_json + render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json} + end + def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) json = serializer.new(data, scope: spree_current_user).to_json render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json} diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb index 5aa36624cd..93b7c96f95 100644 --- a/app/helpers/enterprises_helper.rb +++ b/app/helpers/enterprises_helper.rb @@ -52,17 +52,17 @@ module EnterprisesHelper def shop_trial_in_progress?(enterprise) !!enterprise.shop_trial_start_date && - (enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days > Time.zone.now) && + (enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days > Time.zone.now) && %w(own any).include?(enterprise.sells) end def shop_trial_expired?(enterprise) !!enterprise.shop_trial_start_date && - (enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days <= Time.zone.now) && + (enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days <= Time.zone.now) && %w(own any).include?(enterprise.sells) end def remaining_trial_days(enterprise) - distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days) + distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days) end end diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index b9fb52fb41..30fad48f58 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -4,8 +4,11 @@ class ProducerMailer < Spree::BaseMailer @producer = producer @coordinator = order_cycle.coordinator @order_cycle = order_cycle - @line_items = aggregated_line_items_from(@order_cycle, @producer) + line_items = line_items_from(@order_cycle, @producer) + @grouped_line_items = line_items.group_by(&:product_and_full_name) @receival_instructions = @order_cycle.receival_instructions_for @producer + @total = total_from_line_items(line_items) + @tax_total = tax_total_from_line_items(line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -25,10 +28,6 @@ class ProducerMailer < Spree::BaseMailer line_items_from(order_cycle, producer).any? end - def aggregated_line_items_from(order_cycle, producer) - aggregate_line_items line_items_from(order_cycle, producer) - end - def line_items_from(order_cycle, producer) Spree::LineItem. joins(:order => :order_cycle, :variant => :product). @@ -37,16 +36,11 @@ class ProducerMailer < Spree::BaseMailer merge(Spree::Order.complete) end - def aggregate_line_items(line_items) - # Arrange the items in a hash to group quantities - line_items.inject({}) do |lis, li| - if lis.key? li.variant - lis[li.variant].quantity += li.quantity - else - lis[li.variant] = li - end + def total_from_line_items(line_items) + Spree::Money.new line_items.sum(&:total) + end - lis - end + def tax_total_from_line_items(line_items) + Spree::Money.new line_items.sum(&:included_tax) end end diff --git a/app/models/column_preference.rb b/app/models/column_preference.rb new file mode 100644 index 0000000000..09ad0e55a4 --- /dev/null +++ b/app/models/column_preference.rb @@ -0,0 +1,46 @@ +require 'open_food_network/column_preference_defaults' + +class ColumnPreference < ActiveRecord::Base + extend OpenFoodNetwork::ColumnPreferenceDefaults + + # These are the attributes used to identify a preference + attr_accessible :user_id, :action_name, :column_name + + # These are attributes that need to be mass assignable + attr_accessible :name, :visible + + # Non-persisted attributes that only have one + # setting (ie. the default) for a given column + attr_accessor :name + + belongs_to :user, class_name: "Spree::User" + + validates :action_name, presence: true, inclusion: { in: proc { known_actions } } + validates :column_name, presence: true, inclusion: { in: proc { |p| valid_columns_for(p.action_name) } } + + def self.for(user, action_name) + stored_preferences = where(user_id: user.id, action_name: action_name) + default_preferences = send("#{action_name}_columns") + default_preferences.each_with_object([]) do |(column_name, default_attributes), preferences| + stored_preference = stored_preferences.find_by_column_name(column_name) + if stored_preference + stored_preference.assign_attributes(default_attributes.select{ |k,v| stored_preference[k].nil? }) + preferences << stored_preference + else + attributes = default_attributes.merge(user_id: user.id, action_name: action_name, column_name: column_name) + preferences << ColumnPreference.new(attributes) + end + end + end + + private + + def self.valid_columns_for(action_name) + send("#{action_name}_columns").keys.map(&:to_s) + end + + def self.known_actions + OpenFoodNetwork::ColumnPreferenceDefaults.private_instance_methods + .select{|m| m.to_s.end_with?("_columns")}.map{ |m| m.to_s.sub /_columns$/, ''} + end +end diff --git a/app/models/column_preference_set.rb b/app/models/column_preference_set.rb new file mode 100644 index 0000000000..32b9b85a1b --- /dev/null +++ b/app/models/column_preference_set.rb @@ -0,0 +1,5 @@ +class ColumnPreferenceSet < ModelSet + def initialize(collection, attributes={}) + super(ColumnPreference, collection, attributes, nil, nil ) + end +end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index d3e86ff3ca..9f839f9d08 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -1,6 +1,5 @@ class Enterprise < ActiveRecord::Base SELLS = %w(unspecified none own any) - SHOP_TRIAL_LENGTH = 30 ENTERPRISE_SEARCH_RADIUS = 100 preference :shopfront_message, :text, default: "" @@ -338,7 +337,7 @@ class Enterprise < ActiveRecord::Base end def shop_trial_expiry - shop_trial_start_date.andand + Enterprise::SHOP_TRIAL_LENGTH.days + shop_trial_start_date.andand + Spree::Config[:shop_trial_length_days].days end def can_invoice? @@ -352,20 +351,6 @@ class Enterprise < ActiveRecord::Base end end - def rules_per_tag - tag_rule_map = {} - tag_rules.each do |rule| - rule.preferred_customer_tags.split(",").each do |tag| - if tag_rule_map[tag] - tag_rule_map[tag] += 1 - else - tag_rule_map[tag] = 1 - end - end - end - tag_rule_map - end - protected def devise_mailer diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index e250d1341d..2936a47d41 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -72,7 +72,7 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty - can [:admin, :destroy], TagRule do |tag_rule| + can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule| user.enterprises.include? tag_rule.enterprise end @@ -101,6 +101,10 @@ class AbilityDecorator can [:print], Spree::Order do |order| order.user == user end + + can [:admin, :bulk_update], ColumnPreference do |column_preference| + column_preference.user == user + end end def add_product_management_abilities(user) @@ -218,7 +222,6 @@ class AbilityDecorator can [:create], Customer can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) - can [:admin, :index], :tag end diff --git a/app/models/spree/app_configuration_decorator.rb b/app/models/spree/app_configuration_decorator.rb index c169ce0231..570c7acc1e 100644 --- a/app/models/spree/app_configuration_decorator.rb +++ b/app/models/spree/app_configuration_decorator.rb @@ -20,6 +20,8 @@ Spree::AppConfiguration.class_eval do preference :account_invoices_monthly_rate, :decimal, default: 0 preference :account_invoices_monthly_cap, :decimal, default: 0 preference :account_invoices_tax_rate, :decimal, default: 0 + preference :shop_trial_length_days, :integer, default: 30 + preference :minimum_billable_turnover, :integer, default: 0 # Monitoring preference :last_job_queue_heartbeat_at, :string, default: nil diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb index 6e6405a589..8d385673db 100644 --- a/app/models/tag_rule.rb +++ b/app/models/tag_rule.rb @@ -9,6 +9,8 @@ class TagRule < ActiveRecord::Base attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags + scope :for, lambda { |enterprises| where(enterprise_id: enterprises) } + def set_context(subject, context) @subject = subject @context = context @@ -24,6 +26,19 @@ class TagRule < ActiveRecord::Base end end + def self.mapping_for(enterprises) + self.for(enterprises).inject({}) do |mapping, rule| + rule.preferred_customer_tags.split(",").each do |tag| + if mapping[tag] + mapping[tag][:rules] += 1 + else + mapping[tag] = { text: tag, rules: 1 } + end + end + mapping + end + end + private def relevant? diff --git a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface index 3fa7d3ea83..a6c51d517c 100644 --- a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface +++ b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface @@ -19,11 +19,20 @@ .alpha.six.columns .field %label{for: "order_distributor_id"} Distributor - %select.fullwidth{id: "order_distributor_id", name: "order[distributor_id]", 'ng-model' => 'distributor_id'} - %option{"ng-repeat" => "shop in shops", "ng-value" => "shop.id", "ng-selected" => "distributor_id == shop.id", "ng-disabled" => "!distributorHasOrderCycles(shop)", "ng-bind" => "shop.name"} + %input.ofn-select2.fullwidth{id: "order_distributor_id", + type: 'number', + name: "order[distributor_id]", + "ng-model" => 'distributor_id', + data: "shops" } .omega.six.columns - .field{"ng-show" => "distributor_id"} - %label{for: "order_order_cycle_id"} Order Cycle - %select.select2.fullwidth{id: "order_order_cycle_id", name: "order[order_cycle_id]", 'ng-model' => 'order_cycle_id'} - %option{"ng-repeat" => "oc in orderCycles | filter:validOrderCycle", "ng-value" => "oc.id", "ng-selected" => "order_cycle_id == oc.id", "ng-bind" => "oc.name_and_status"} + .field + %label{ for: "order_order_cycle_id"} Order Cycle + %input.ofn-select2.fullwidth{id: "order_order_cycle_id", + type: 'number', + name: "order[order_cycle_id]", + "ng-model" => 'order_cycle_id', + "ng-disabled" => "!distributor_id", + data: "orderCycles", + text: "name_and_status", + filter: "validOrderCycle" } diff --git a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface index a2f9ed766a..3cffbbabcb 100644 --- a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface +++ b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface @@ -54,7 +54,7 @@ = f.label :tags, t(:tags) .omega.eight.columns = f.hidden_field :tag_list, "ng-value" => "shippingMethod.tag_list" - %tags-with-translation#something{ object: "shippingMethod" } + %tags-with-translation#something{ object: "shippingMethod", 'find-tags' => 'findTags(query)' } .row .alpha.eleven.columns diff --git a/app/serializers/api/admin/column_preference_serializer.rb b/app/serializers/api/admin/column_preference_serializer.rb new file mode 100644 index 0000000000..8b8eeb1b3a --- /dev/null +++ b/app/serializers/api/admin/column_preference_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::ColumnPreferenceSerializer < ActiveModel::Serializer + attributes :id, :user_id, :action_name, :column_name, :name, :visible +end diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 052f49a917..4634625c11 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -6,10 +6,9 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer end def tags - tag_rule_map = object.enterprise.rules_per_tag object.tag_list.map do |tag| - { text: tag, rules: tag_rule_map[tag] } + tag_rule_map = options[:tag_rule_mapping][tag] + tag_rule_map || { text: tag, rules: nil } end end - end diff --git a/app/views/admin/business_model_configuration/edit.html.haml b/app/views/admin/business_model_configuration/edit.html.haml index 89345178f9..04ee47e45d 100644 --- a/app/views/admin/business_model_configuration/edit.html.haml +++ b/app/views/admin/business_model_configuration/edit.html.haml @@ -17,10 +17,16 @@ Adjust the amount that enterprises will be billed each month for use of the OFN. %br = form_for @settings, as: :settings, url: main_app.admin_business_model_configuration_path, :method => :put do |f| + .row + .three.columns.alpha + = f.label :shop_trial_length_days, t(:shop_trial_length) + %span.icon-question-sign{'ofn-with-tip' => "The length of time (in days) that enterprises who are set up as shops can run as a trial period."} + .two.columns.omega + = f.number_field :shop_trial_length_days, min: 0.0, step: 1.0, class: "fullwidth" .row .three.columns.alpha = f.label :account_invoices_monthly_fixed, t(:fixed_monthly_charge) - %span.icon-question-sign{'ofn-with-tip' => "A fixed monthly charge for ALL enterprises who are set up as a shop, regardless of how much produce they sell."} + %span.icon-question-sign{'ofn-with-tip' => "A fixed monthly charge for all enterprises who are set up as a shop and have exceeded the minimum billable turnover (if set)."} .two.columns.omega .input-symbol.before %span= Spree::Money.currency_symbol @@ -45,6 +51,14 @@ %span.icon-question-sign{'ofn-with-tip' => "Tax rate that applies to the the monthly bill that enterprises are charged for using the system."} .two.columns.omega = f.number_field :account_invoices_tax_rate, min: 0.0, max: 1.0, step: 0.01, class: "fullwidth", 'watch-value-as' => 'taxRate' + .row + .three.columns.alpha + = f.label :minimum_billable_turnover, t(:minimum_monthly_billable_turnover) + %span.icon-question-sign{'ofn-with-tip' => "Minimum monthly turnover before a shopfront will be charged for using OFN. Enterprises turning over less than this amount in a month will not be charged, either as a percentage or fixed rate."} + .two.columns.omega + .input-symbol.before + %span= Spree::Money.currency_symbol + = f.number_field :minimum_billable_turnover, min: 0, class: "fullwidth", 'watch-value-as' => 'minBillableTurnover' .row .five.columns.alpha.omega.form-buttons{"data-hook" => "buttons"} @@ -78,10 +92,12 @@ = label_tag :included_tax, t(:included_tax) %span.icon-question-sign{'ofn-with-tip' => "The total tax included in the example monthly bill, given the settings and the turnover provided."} .two.columns.omega - %input.fullwidth{ id: 'included_tax', type: "text", readonly: true, ng: { value: 'includedTax() | currency' } } + %span= Spree::Money.currency_symbol + %input.fullwidth{ id: 'included_tax', type: "text", readonly: true, ng: { value: 'includedTax()' } } .row .three.columns.alpha = label_tag :total_incl_tax, t(:total_monthly_bill_incl_tax) %span.icon-question-sign{'ofn-with-tip' => "The example total monthly bill with tax included, given the settings and the turnover provided."} .two.columns.omega - %input.fullwidth{ id: 'total_incl_tax', type: "text", readonly: true, ng: { value: 'total() | currency' } } + %span= Spree::Money.currency_symbol + %input.fullwidth{ id: 'total_incl_tax', type: "text", readonly: true, ng: { value: 'total()' } } diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 0a348d4a0a..c570632c64 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -2,39 +2,50 @@ %h1.page-title =t :customers +- content_for :app_wrapper_attrs do + = "ng-app='admin.customers'" + +- content_for :page_actions do + %li + %a.button.icon-plus#new-customer{ href: "#", "new-customer-dialog" => true } + = t('admin.customers.index.new_customer') + += admin_inject_column_preferences module: 'admin.customers' = admin_inject_shops -%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } } - .row{ ng: { hide: "loaded && customers.length > 0" } } +%div{ ng: { controller: 'customersCtrl' } } + .row{ ng: { hide: "!RequestMonitor.loading && customers.length > 0" } } .five.columns.alpha %h3 =t :please_select_hub .four.columns - %select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' } + %select.select2.fullwidth#shop_id{ 'ng-model' => 'CurrentShop.shop', name: 'shop_id', 'ng-options' => 'shop as shop.name for shop in shops' } .seven.columns.omega   - .row{ 'ng-hide' => '!loaded || customers.length == 0' } + .row{ 'ng-hide' => 'RequestMonitor.loading || !CurrentShop.shop.id || customers.length == 0' } .controls.sixteen.columns.alpha.omega .five.columns.alpha %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } .eight.columns   - = render 'admin/shared/columns_dropdown' - .row{ 'ng-if' => 'shop.id && !loaded' } + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } + .row{ 'ng-if' => 'CurrentShop.shop.id && RequestMonitor.loading' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 =t :loading_customers - .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredCustomers.length == 0'} + .row{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredCustomers.length == 0'} %h1#no_results =t :no_customers_found + .row.margin-bottom-50{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } + %form{ name: "customers_form" } + %save-bar{ dirty: "customers_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "submitAll(customers_form)", disabled: "!customers_form.$dirty" } } - .row{ ng: { show: "loaded && filteredCustomers.length > 0" } } - %form{ name: "customers" } %table.index#customers - %col.email{ width: "20%"} - %col.code{ width: "20%"} - %col.tags{ width: "50%"} + %col.email{ width: "20%", 'ng-show' => 'columns.email.visible' } + %col.code{ width: "20%", 'ng-show' => 'columns.code.visible' } + %col.tags{ width: "50%", 'ng-show' => 'columns.tags.visible' } %col.actions{ width: "10%"} %thead %tr{ ng: { controller: "ColumnsCtrl" } } @@ -48,10 +59,10 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } -# %td.bulk -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } - %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} + %td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' } %td.code{ 'ng-show' => 'columns.code.visible' } %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } %td.tags{ 'ng-show' => 'columns.tags.visible' } @@ -59,12 +70,9 @@ %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' } - %form{ng: {show: "loaded", submit: 'add(newCustomerEmail)'}} - %h2= t '.add_new_customer' - .row - .five.columns.alpha - %input.fullwidth{type: "text", placeholder: t('.customer_placeholder'), ng: {model: 'newCustomerEmail'}} - .eleven.columns.omega - %input{type: "submit", value: t('.add_customer')} + -# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" } + %div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } } + %input{ type: 'button', value: 'Show More', ng: { click: 'customerLimit = customerLimit + 20' } } + or + %input{ type: 'button', value: "Show All ({{ filteredCustomers.length - customerLimit }} More)", ng: { click: 'customerLimit = filteredCustomers.length' } } diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 1fb35e595c..75dbf98f17 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -6,7 +6,7 @@ .six.columns   -# = render 'admin/shared/bulk_actions_dropdown' .three.columns   - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } .row{ 'ng-if' => '!loaded' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } @@ -15,7 +15,7 @@ %h1#no_results No enterprises found. - .row{ ng: { show: "loaded && filteredEnterprises.length > 0" }, bindonce: true } + .row{ ng: { show: "loaded && filteredEnterprises.length > 0" } } %table.index#enterprises %col.name{ width: "28%", ng: { show: 'columns.name.visible' } } %col.producer{ width: "18%", ng: { show: 'columns.producer.visible' }} @@ -24,24 +24,23 @@ %col.manage{ width: "18%", ng: { show: 'columns.manage.visible' }} %thead %tr{ ng: { controller: "ColumnsCtrl" } } - %th.name{ ng: { show: 'columns.name.visible' } } - Name - %th.producer{ ng: { show: 'columns.producer.visible' } } Producer? - %th.package{ ng: { show: 'columns.package.visible' } } Package - %th.status{ ng: { show: 'columns.status.visible' } } Status - %th.manage{ ng: { show: 'columns.manage.visible' } } Manage + %th.name{ ng: { show: 'columns.name.visible' } }=t('admin.name') + %th.producer{ ng: { show: 'columns.producer.visible' } }=t('.producer?') + %th.package{ ng: { show: 'columns.package.visible' } }=t('.package') + %th.status{ ng: { show: 'columns.status.visible' } }=t('.status') + %th.manage{ ng: { show: 'columns.manage.visible' } }=t('.manage') %tbody{ :id => "e_{{enterprise.id}}", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )", controller: 'EnterpriseIndexRowCtrl' } } %tr.enterprise.panel-toggle-row{ object: "enterprise", ng: { class: { even: "'even'", odd: "'odd'"} } } %td.name{ ng: { show: 'columns.name.visible' } } - %span{ bo: { bind: "enterprise.name" } } + %span{ ng: { bind: "::enterprise.name" } } %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: producerError}" }, name: "producer" } %h5{ ng: { bind: "producer" } } %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: packageError}" }, name: "package" } %h5{ ng: { bind: "package" } } %td.status.panel-toggle.text-center{ ng: { show: 'columns.status.visible' }, name: "status" } - %i.icon-status{ bo: { class: "status" } } + %i.icon-status{ ng: { class: "::status()" } } %td.manage{ ng: { show: 'columns.manage.visible' } } - %a.button.fullwidth{ bo: { href: 'enterprise.edit_path' } } + %a.button.fullwidth{ ng: { href: '{{::enterprise.edit_path}}' } } Manage %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml index 1a50e4f353..4ed9aeb5c1 100644 --- a/app/views/admin/enterprises/form/_tag_rules.html.haml +++ b/app/views/admin/enterprises/form/_tag_rules.html.haml @@ -3,7 +3,7 @@ .eleven.columns.alpha.omega .no_tags{ ng: { show: "tagGroups.length == 0" } } No tags apply to this enterprise yet - .customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true } + .customer_tag{ ng: { repeat: "tagGroup in tagGroups" } } .header %table %colgroup @@ -23,8 +23,8 @@ %table %tr.tag_rule{ id: "tr_{{rule.id}}", ng: { repeat: "rule in tagGroup.rules" } } %td - %discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } } - %filter-shipping-methods{ bo: { if: "rule.type == 'TagRule::FilterShippingMethods'" } } + %discount-order{ ng: { if: "::rule.type == 'TagRule::DiscountOrder'" } } + %filter-shipping-methods{ ng: { if: "::rule.type == 'TagRule::FilterShippingMethods'" } } %td.actions %a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" } .add_rule.text-center diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 62bc881728..b84659738c 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -9,6 +9,8 @@ = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' = admin_inject_monthly_bill_description += admin_inject_column_preferences module: 'admin.enterprises', action: "enterprises_index" + = render 'admin/shared/enterprises_sub_menu' = render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_set } diff --git a/app/views/admin/order_cycles/_advanced_settings.html.haml b/app/views/admin/order_cycles/_advanced_settings.html.haml index 27b347880b..f8f71d0b93 100644 --- a/app/views/admin/order_cycles/_advanced_settings.html.haml +++ b/app/views/admin/order_cycles/_advanced_settings.html.haml @@ -5,7 +5,7 @@ = form_for [main_app, :admin, @order_cycle] do |f| .row .six.columns.alpha - = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycle.choose_products_from') + = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from') .with-tip{'data-powertip' => "You can opt to restrict all available products (both incoming and outgoing), to only those in #{@order_cycle.coordinator.name}'s inventory."} %a What's this? .four.columns diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index c447d35d58..e53fd515b0 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -51,15 +51,9 @@ .actions - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path + = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' + %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' - unless Rails.env.production? diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml index 703d8094b9..39fa19f287 100644 --- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -3,14 +3,14 @@ = f.label :name .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true + = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()' - else {{ order_cycle.name }} .two.columns = f.label :orders_open_at, 'Orders open' .omega.six.columns - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at' + = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at', 'ng-disabled' => '!loaded()' - else {{ order_cycle.orders_open_at }} @@ -23,6 +23,6 @@ = f.label :orders_close_at, 'Orders close' .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at' + = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at', 'ng-disabled' => '!loaded()' - else {{ order_cycle.orders_close_at }} diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 4dc64a012b..ba28526f7b 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -22,13 +22,6 @@ .actions - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' + = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index f3b0bdb721..4712566dfd 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -27,7 +27,13 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller} do |f| += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } + %input.red{ type: "button", value: "Update", ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } } + %input.red{ type: "button", value: "Update and Close", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } + %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } + - if order_cycles_simple_form = render 'simple_form', f: f - else diff --git a/app/views/admin/order_cycles/set_coordinator.html.haml b/app/views/admin/order_cycles/set_coordinator.html.haml index db9bbb926c..f4c06e0a98 100644 --- a/app/views/admin/order_cycles/set_coordinator.html.haml +++ b/app/views/admin/order_cycles/set_coordinator.html.haml @@ -1,5 +1,5 @@ %h4.text-center - =t'select_a_coordinator_for_your_order_cycle' + =t 'select_a_coordinator_for_your_order_cycle' %br diff --git a/app/views/admin/shared/_columns_dropdown.html.haml b/app/views/admin/shared/_columns_dropdown.html.haml deleted file mode 100644 index 4663443321..0000000000 --- a/app/views/admin/shared/_columns_dropdown.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.ofn-drop-down.right#columns-dropdown - %span{ :class => 'icon-reorder' }= "  #{t('admin.columns')}".html_safe - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ ng: { repeat: "column in columns" }, toggle: { column: true } } - %span.check - %span.name {{column.name }} diff --git a/app/views/admin/variant_overrides/_controls.html.haml b/app/views/admin/variant_overrides/_controls.html.haml index 4a25677be3..900f46e430 100644 --- a/app/views/admin/variant_overrides/_controls.html.haml +++ b/app/views/admin/variant_overrides/_controls.html.haml @@ -3,13 +3,13 @@ .eight.columns.alpha = render 'admin/shared/bulk_actions_dropdown' = render 'admin/shared/views_dropdown' - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.inventory.inventory_powertip')}" } } - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.inventory.hidden_powertip')}" } } - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.inventory.new_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.inventory_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.hidden_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.new_powertip')}" } } .four.columns   .four.columns.omega{ ng: { show: 'views.new.visible' } } %button.fullwidth{ type: 'button', ng: { click: "selectView('inventory')" } } %i.icon-chevron-left Back to my inventory .four.columns.omega{ng: { show: 'views.inventory.visible' } } - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/admin/variant_overrides/_data.html.haml b/app/views/admin/variant_overrides/_data.html.haml index 9d371415fe..02c52d747d 100644 --- a/app/views/admin/variant_overrides/_data.html.haml +++ b/app/views/admin/variant_overrides/_data.html.haml @@ -2,5 +2,6 @@ = admin_inject_hubs module: 'admin.variantOverrides' = admin_inject_hub_permissions = admin_inject_producers module: 'admin.variantOverrides' += admin_inject_column_preferences module: 'admin.variantOverrides' = admin_inject_variant_overrides = admin_inject_inventory_items module: 'admin.variantOverrides' diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml index c9f3a27b32..a1772ed222 100644 --- a/app/views/admin/variant_overrides/_filters.html.haml +++ b/app/views/admin/variant_overrides/_filters.html.haml @@ -5,7 +5,7 @@ %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub_id'} } .two.columns   .filter_select.four.columns - %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.inventory.select_a_shop')}'" } } + %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } } %br %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs' } } .filter_select.four.columns diff --git a/app/views/admin/variant_overrides/_header.html.haml b/app/views/admin/variant_overrides/_header.html.haml index e4a8e98420..64a6bfb201 100644 --- a/app/views/admin/variant_overrides/_header.html.haml +++ b/app/views/admin/variant_overrides/_header.html.haml @@ -1,8 +1,8 @@ - content_for :html_title do - = t("admin.inventory.title") + = t("admin.variant_overrides.index.title") - content_for :page_title do - %h1.page-title= t("admin.inventory.title") - %a.with-tip{ 'data-powertip' => "#{t("admin.inventory.description")}" }=t('admin.whats_this') + %h1.page-title= t("admin.variant_overrides.index.title") + %a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this') = render :partial => 'spree/admin/shared/product_sub_menu' diff --git a/app/views/admin/variant_overrides/_hidden_products.html.haml b/app/views/admin/variant_overrides/_hidden_products.html.haml index 902e24a232..ed3e2555c8 100644 --- a/app/views/admin/variant_overrides/_hidden_products.html.haml +++ b/app/views/admin/variant_overrides/_hidden_products.html.haml @@ -9,14 +9,14 @@ %th.producer=t('admin.producer') %th.product=t('admin.product') %th.variant=t('(admin.variant') - %th.add=t('admin.inventory.add') - %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } + %th.add=t('admin.variant_overrides.index.add') + %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } - %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ bo: { bind: 'product.name'} } + %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { bind: '::product.name'} } %td.variant - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } - = t('admin.inventory.add') + = t('admin.variant_overrides.index.add') diff --git a/app/views/admin/variant_overrides/_new_products.html.haml b/app/views/admin/variant_overrides/_new_products.html.haml index 86ca180b8f..99827717f5 100644 --- a/app/views/admin/variant_overrides/_new_products.html.haml +++ b/app/views/admin/variant_overrides/_new_products.html.haml @@ -8,19 +8,19 @@ %tr %th.producer=t('admin.producer') %th.product=t('admin.product') - %th.variant=t('(admin.variant') - %th.add=t('admin.inventory.add') - %th.hide=t('admin.inventory.hide') - %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } + %th.variant=t('admin.variant') + %th.add=t('admin.variant_overrides.index.add') + %th.hide=t('admin.variant_overrides.index.hide') + %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } - %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ bo: { bind: 'product.name'} } + %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { bind: '::product.name'} } %td.variant - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } - = t('admin.inventory.add') + = t('admin.variant_overrides.index.add') %td.hide %button.fullwidth.hide.icon-remove{ ng: { click: "setVisibility(hub_id,variant.id,false)" } } - = t('admin.inventory.hide') + = t('admin.variant_overrides.index.hide') diff --git a/app/views/admin/variant_overrides/_new_products_alert.html.haml b/app/views/admin/variant_overrides/_new_products_alert.html.haml index 29ec4c9623..385616d817 100644 --- a/app/views/admin/variant_overrides/_new_products_alert.html.haml +++ b/app/views/admin/variant_overrides/_new_products_alert.html.haml @@ -1,5 +1,5 @@ %div{ ng: { show: '(newProductCount = (products | hubPermissions:hubPermissions:hub_id | newInventoryProducts:hub_id).length) > 0 && !views.new.visible && !alertDismissed' } } %hr.divider.sixteen.columns.alpha.omega - %alert-row{ message: "#{t('admin.inventory.new_products_alert_message', new_product_count: '{{ newProductCount }}')}", + %alert-row{ message: "#{t('admin.variant_overrides.index.new_products_alert_message', new_product_count: '{{ newProductCount }}')}", dismissed: "alertDismissed", - button: { text: "#{t('admin.inventory.review_now')}", action: "selectView('new')" } } + button: { text: "#{t('admin.variant_overrides.index.review_now')}", action: "selectView('new')" } } diff --git a/app/views/admin/variant_overrides/_no_results.html.haml b/app/views/admin/variant_overrides/_no_results.html.haml index cdec6ab8c5..7ec4e68f0e 100644 --- a/app/views/admin/variant_overrides/_no_results.html.haml +++ b/app/views/admin/variant_overrides/_no_results.html.haml @@ -1,7 +1,7 @@ %div.text-big.no-results{ ng: { show: 'hub_id && products.length > 0 && filteredProducts.length == 0' } } - %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.inventory.currently_empty') - %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.inventory.no_matching_products') - %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.inventory.no_hidden_products') - %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.inventory.no_matching_hidden_products') - %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.inventory.no_new_products') - %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.inventory.no_matching_new_products') + %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.currently_empty') + %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_products') + %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_hidden_products') + %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_hidden_products') + %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_new_products') + %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_new_products') diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index c17e4c189a..4330496b3e 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,6 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ save: "update()", form: "variant_overrides_form" } + %save-bar{ dirty: "customers_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } @@ -15,13 +16,13 @@ %tr{ ng: { controller: "ColumnsCtrl" } } %th.producer{ ng: { show: 'columns.producer.visible' } }=t('admin.producer') %th.product{ ng: { show: 'columns.product.visible' } }=t('admin.product') - %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.inventory.sku') - %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.inventory.price') - %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.inventory.on_hand') - %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.inventory.on_demand') - %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.inventory.enable_reset') - %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.inventory.inherit') - %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.inventory.hide') - %tbody{bindonce: true, ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } + %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.sku') + %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.price') + %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.on_hand') + %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.on_demand?') + %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.variant_overrides.index.enable_reset?') + %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.variant_overrides.index.inherit?') + %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.variant_overrides.index.hide') + %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 70b48e3909..119ee867df 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,6 +1,6 @@ %tr.product.even - %td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} } + %td.producer{ ng: { show: 'columns.producer.visible', bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { show: 'columns.product.visible', bind: '::product.name'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %td.price{ ng: { show: 'columns.price.visible' } } %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index c26be92697..c139bc7ffb 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -1,8 +1,8 @@ %tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'}} %td.producer{ ng: { show: 'columns.producer.visible' } } %td.product{ ng: { show: 'columns.product.visible' } } - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'} %td.price{ ng: { show: 'columns.price.visible' } } @@ -19,4 +19,4 @@ %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true } %td.visibility{ ng: { show: 'columns.visibility.visible' } } %button.icon-remove.hide.fullwidth{ :type => 'button', ng: { click: "setVisibility(hub_id,variant.id,false)" } } - = t('admin.inventory.hide') + = t('admin.variant_overrides.index.hide') diff --git a/app/views/admin/variant_overrides/_show_more.html.haml b/app/views/admin/variant_overrides/_show_more.html.haml index 8d60593ddc..21e927e443 100644 --- a/app/views/admin/variant_overrides/_show_more.html.haml +++ b/app/views/admin/variant_overrides/_show_more.html.haml @@ -1,4 +1,5 @@ -.text-center +-# %show-more.text-center{ data: "filteredProducts", limit: "productLimit", increment: "10" } +.text-center{ ng: { show: "filteredProducts.length > productLimit" } } %input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } } or %input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } } diff --git a/app/views/groups/_contact.html.haml b/app/views/groups/_contact.html.haml index 320da464bd..5c1e3aef5e 100644 --- a/app/views/groups/_contact.html.haml +++ b/app/views/groups/_contact.html.haml @@ -1,4 +1,4 @@ -%div.contact-container{bindonce: true} +%div.contact-container - if @group.email.present? || @group.website.present? || @group.phone.present? %div.modal-centered %p.modal-header @@ -16,12 +16,12 @@ =link_to_service "http://", @group.website do = t :groups_contact_website -%div{bindonce: true} +%div - if @group.facebook.present? || @group.twitter.present? || @group.linkedin.present? || @group.instagram.present? %div.modal-centered.pad-top %p.modal-header = t :groups_contact_web - .follow-icons{bindonce: true} + .follow-icons =link_to_service "http://twitter.com/", @group.twitter do %i.ofn-i_041-twitter =link_to_service "https://www.facebook.com/", @group.facebook do @@ -30,8 +30,8 @@ %i.ofn-i_042-linkedin =link_to_service "http://instagram.com/", @group.instagram do %i.ofn-i_043-instagram - -%div{bindonce: true} + +%div - if @group.address1.present? || @group.city.present? %div.modal-centered.pad-top %p.modal-header @@ -46,5 +46,3 @@ = @group.city = @group.state = @group.zipcode - - diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml index afb19e1b91..ad93d4f366 100644 --- a/app/views/groups/index.html.haml +++ b/app/views/groups/index.html.haml @@ -25,14 +25,14 @@ .group.animate-repeat{"ng-repeat" => "group in groups = (Groups.groups | groups:query | orderBy:order)", name: "group{{group.id}}", id: "group{{group.id}}"} - .row.pad-top{bindonce: true} + .row.pad-top .small-12.medium-6.columns .groups-header - %a{"bo-href-i" => "/groups/{{group.permalink}}"} + %a{"ng-href" => "/groups/{{::group.permalink}}"} %i.ofn-i_035-groups - %span.group-name{"bo-text" => "group.name"} + %span.group-name{"ng-bind" => "::group.name"} .small-3.medium-2.columns - %p{"bo-text" => "group.state"} + %p{"ng-bind" => "::group.state"} .small-9.medium-4.columns.groups-icons %p %link-to-service.ofn-i_050-mail-circle{service: '""', ref: 'group.email.split("").reverse().join("")', mailto: true} diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 74588176b9..af497a4195 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -32,26 +32,27 @@ .small-12.columns.pad-top .row .small-12.medium-12.large-9.columns - %div{"ng-controller" => "TabsCtrl"} + %div{"ng-controller" => "GroupTabsCtrl"} %tabset %tab{heading: t(:label_map), - active: "active(\'\')", - select: "select(\'\')"} + active: "tabs.map.active", + select: "select(\'map\')"} .map-container - %map{"ng-if" => "(active(\'\') && (mapShowed = true)) || mapShowed"} + %map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"} %google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"} + %map-search %markers{models: "mapMarkers", fit: "true", coords: "'self'", icon: "'icon'", click: "'reveal'"} %tab{heading: t(:groups_about), - active: "active(\'about\')", + active: "tabs.about.active", select: "select(\'about\')"} %h1 = t :groups_about %p!= @group.long_description %tab{heading: t(:groups_producers), - active: "active(\'producers\')", + active: "tabs.producers.active", select: "select(\'producers\')"} .producers{"ng-controller" => "GroupEnterprisesCtrl"} .row @@ -61,7 +62,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "producers/filters" - .row{bindonce: true} + .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", @@ -77,7 +78,7 @@ = render partial: 'shared/components/enterprise_no_results' %tab{heading: t(:groups_hubs), - active: "active(\'hubs\')", + active: "tabs.hubs.active", select: "select(\'hubs\')"} .hubs{"ng-controller" => "GroupEnterprisesCtrl"} .row @@ -88,7 +89,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "hub_filters" - .row{bindonce: true} + .row .small-12.columns .active_table %hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}", diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index b96b13885e..4e90771cc1 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -1,45 +1,45 @@ -.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}", bindonce: true} +.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} .columns.small-12.medium-6.large-5.fat - %div{"bo-if" => "hub.taxons"} + %div{"ng-if" => "::hub.taxons"} %label = t :hubs_buy .trans-sentence %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"} %render-svg{path: "{{taxon.icon}}"} - %span{"bo-text" => "taxon.name"} - %div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"} + %span{"ng-bind" => "::taxon.name"} + %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}   .columns.small-12.medium-3.large-2.fat - %div{"bo-if" => "hub.pickup || hub.delivery"} + %div{"ng-if" => "::(hub.pickup || hub.delivery)"} %label = t :hubs_delivery_options %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1 - %li.pickup{"bo-if" => "hub.pickup"} + %li.pickup{"ng-if" => "::hub.pickup"} %i.ofn-i_038-takeaway = t :hubs_pickup - %li.delivery{"bo-if" => "hub.delivery"} + %li.delivery{"ng-if" => "::hub.delivery"} %i.ofn-i_039-delivery = t :hubs_delivery .columns.small-12.medium-3.large-5.fat - %div{"bo-if" => "hub.producers"} + %div{"ng-if" => "::hub.producers"} %label = t :hubs_producers %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"} %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"} %enterprise-modal %i.ofn-i_036-producers - %span{"bo-text" => "enterprise.name"} - %li{"data-is-link" => "true", "class" => "more-producers-link", "bo-show" => "hub.producers.length>7"} + %span{"ng-bind" => "::enterprise.name"} + %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"} %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers"} .more + - %span{"bo-text" => "hub.producers.length-7"} + %span{"ng-bind" => "::hub.producers.length-7"} = t :label_more .less = t :label_less %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"} %enterprise-modal %i.ofn-i_036-producers - %span{"bo-text" => "enterprise.name"} - %div.show-for-medium-up{"bo-if" => "hub.producers.length==0"} + %span{"ng-bind" => "::enterprise.name"} + %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"}   diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index 85e200fb94..fbc008a5c6 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,46 +1,46 @@ -.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true} +.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} .columns.small-12.medium-5.large-5.skinny-head - %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"} - %i{bo: {class: "hub.icon_font"}} - %span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"} + %a.hub{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"} + %i{ng: {class: "::hub.icon_font"}} + %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} .columns.small-4.medium-2.large-2 - %span.margin-top{"bo-text" => "hub.address.city"} + %span.margin-top{"ng-bind" => "::hub.address.city"} .columns.small-2.medium-1.large-1 - %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"} %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km) - .columns.small-4.medium-3.large-3.text-right{"bo-if" => "hub.active"} - %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::hub.active"} + %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} %i.ofn-i_033-open-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ ng: { if: "::current()" } } %em= t :hubs_shopping_here - %span.margin-top{ bo: { if: "!current()" } } - %span{"bo-bind" => "hub.orders_close_at | sensible_timeframe"} + %span.margin-top{ ng: { if: "::!current()" } } + %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"} - .columns.small-4.medium-3.large-3.text-right{"bo-if" => "!hub.active"} - %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::!hub.active"} + %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} %i.ofn-i_032-closed-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ ng: { if: "::current()" } } %em= t :hubs_shopping_here - %span.margin-top{ bo: { if: "!current()" } } + %span.margin-top{ ng: { if: "::!current()" } } = t :hubs_orders_closed .columns.small-2.medium-1.large-1.text-right %span.margin-top %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} -.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed", bindonce: true} +.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"} .columns.small-12.medium-6.large-5.skinny-head %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"} %i{ng: {class: "hub.icon_font"}} - %span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"} + %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} .columns.small-4.medium-2.large-2 - %span.margin-top{"bo-text" => "hub.address.city"} + %span.margin-top{"ng-bind" => "::hub.address.city"} .columns.small-2.medium-1.large-1 - %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"} .columns.small-6.medium-3.large-4.text-right - %span.margin-top{ bo: { if: "!current()" } } + %span.margin-top{ ng: { if: "::!current()" } } %em= t :hubs_profile_only diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml new file mode 100644 index 0000000000..b69dde8e66 --- /dev/null +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -0,0 +1,74 @@ +%p + = t :producer_mail_greeting + #{" " + @producer.name}, +%p + = t :producer_mail_text_before + - if @receival_instructions + %p + %b + =t :producer_mail_delivery_instructions + = @receival_instructions +%p + = t :producer_mail_order_text + %table.order-summary + %thead + %tr + %th + = t :sku + %th + = t :supplier + %th + = t :product + %th.text-right + = t :quantity + %th.text-right + = t :price + %th.text-right + = t :subtotal + %th.text-right + = t :included_tax + %tbody + - @grouped_line_items.each_pair do |product_and_full_name, line_items| + %tr + %td + #{line_items.first.variant.sku} + %td + #{raw(line_items.first.product.supplier.name)} + %td + #{raw(product_and_full_name)} + %td.text-right + #{line_items.sum(&:quantity)} + %td.text-right + #{line_items.first.single_money} + %td.text-right + #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency) } + %td.tax.text-right + #{Spree::Money.new(line_items.sum(&:included_tax), currency: line_items.first.currency) } + %tr.total-row + %td + %td + %td + %td + %td + %td.text-right + #{@total} + %td.text-right + #{@tax_total} +%p + = t :producer_mail_text_after +%p + #{t(:producer_mail_signoff)}, + %em + %p + #{@coordinator.name} + %p + %br + #{@coordinator.address.address1} + %br + #{@coordinator.address.city} + %br + #{@coordinator.address.zipcode} + %p + #{@coordinator.phone} + %p + #{@coordinator.email} diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index 93748395ce..b5b644bf01 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -1,21 +1,26 @@ -Dear #{@producer.name}, +#{t :producer_mail_greeting} #{@producer.name}, \ -We now have all the consumer orders for the next food drop. += t :producer_mail_text_before \ - if @receival_instructions - Stock pickup/delivery instructions: + = t :producer_mail_delivery_instructions = @receival_instructions \ Orders summary ================ \ -Here is a summary of the orders for your products: += t :producer_mail_order_text \ -- @line_items.each_pair do |variant, line_item| - #{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_full_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount} +- @grouped_line_items.each_pair do |product_and_full_name, line_items| + #{line_items.first.variant.sku} - #{raw(line_items.first.product.supplier.name)} - #{raw(product_and_full_name)} (QTY: #{line_items.sum(&:quantity)}) @ #{line_items.first.single_money} = #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency)} \ -Thanks and best wishes, +\ +Total: #{@total} +\ += t :producer_mail_text_after + +#{t :producer_mail_signoff}, #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} #{@coordinator.phone} diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 9faa239c02..6b665b4f34 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -2,78 +2,78 @@ .columns.small-12.medium-7.large-7.fat / Will add in long description available once clean up HTML formatting producer.long_description - %div{"bo-if" => "producer.description"} + %div{"ng-if" => "::producer.description"} %label = t :producers_about - %img.right.show-for-medium-up{"bo-src" => "producer.logo" } - %p.text-small{ "bo-text" => "producer.description"} - %div.show-for-medium-up{"bo-if" => "producer.description.length==0"} + %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" } + %p.text-small{ "ng-bind" => "::producer.description"} + %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"} %label   .columns.small-12.medium-5.large-5.fat - %div{"bo-if" => "producer.supplied_taxons"} + %div{"ng-if" => "::producer.supplied_taxons"} %label = t :producers_buy %p.trans-sentence %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} %render-svg{path: "{{taxon.icon}}"} - %span{"bo-text" => "taxon.name"} + %span{"ng-bind" => "::taxon.name"} %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}   - %div{"bo-if" => "producer.email_address || producer.website || producer.phone"} + %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"} %label = t :producers_contact - %p.word-wrap{"bo-if" => "producer.phone"} + %p.word-wrap{"ng-if" => "::producer.phone"} = t :producers_contact_phone - %span{"bo-text" => "producer.phone"} + %span{"ng-bind" => "::producer.phone"} - %p.word-wrap{"bo-if" => "producer.email_address"} - %a{"bo-href" => "producer.email_address | stripUrl", target: "_blank", mailto: true} - %span.email{"bo-bind" => "producer.email_address | stripUrl"} + %p.word-wrap{"ng-if" => "::producer.email_address"} + %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true} + %span.email{"ng-bind" => "::producer.email_address | stripUrl"} - %p.word-wrap{"bo-if" => "producer.website"} - %a{"bo-href-i" => "http://{{producer.website | stripUrl}}", target: "_blank" } - %span{"bo-bind" => "producer.website | stripUrl"} + %p.word-wrap{"ng-if" => "::producer.website"} + %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" } + %span{"ng-bind" => "::producer.website | stripUrl"} - %div{"bo-if" => "producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} + %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} %label = t :producers_social - .follow-icons{bindonce: true} - %span{"bo-if" => "producer.twitter"} - %a{"bo-href-i" => "http://twitter.com/{{producer.twitter}}", target: "_blank"} + .follow-icons + %span{"ng-if" => "::producer.twitter"} + %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"} %i.ofn-i_041-twitter - %span{"bo-if" => "producer.facebook"} - %a{"bo-href-i" => "http://{{producer.facebook | stripUrl}}", target: "_blank"} + %span{"ng-if" => "::producer.facebook"} + %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"} %i.ofn-i_044-facebook - %span{"bo-if" => "producer.linkedin"} - %a{"bo-href-i" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"} + %span{"ng-if" => "::producer.linkedin"} + %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"} %i.ofn-i_042-linkedin - %span{"bo-if" => "producer.instagram"} - %a{"bo-href-i" => "http://instagram.com/{{producer.instagram}}", target: "_blank"} + %span{"ng-if" => "::producer.instagram"} + %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"} %i.ofn-i_043-instagram -.row.active_table_row.pad-top{"ng-if" => "open()", "bo-if" => "producer.hubs"} +.row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs"} .columns.small-12 .row .columns.small-12.fat - %div{"bo-if" => "producer.name"} + %div{"ng-if" => "::producer.name"} %label - = t :producers_buy_at_html, {enterprise: ''.html_safe} - %div.show-for-medium-up{"bo-if" => "!producer.name"} + = t :producers_buy_at_html, {enterprise: ''.html_safe} + %div.show-for-medium-up{"ng-if" => "::!producer.name"}   .row.cta-container .columns.small-12 %a.cta-hub{"ng-repeat" => "hub in producer.hubs | visible | orderBy:'-active'", - "bo-href" => "hub.path", "ofn-change-hub" => "hub", - "bo-class" => "{primary: hub.active, secondary: !hub.active}"} - %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - .hub-name{"bo-text" => "hub.name"} - .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"} + "ng-href" => "{{::hub.path}}", "ofn-change-hub" => "hub", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}"} + %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"} + %i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"} + .hub-name{"ng-bind" => "::hub.name"} + .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"} diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index cf066be05a..311de9daf5 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -1,20 +1,20 @@ .row.active_table_row{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} .columns.small-12.medium-4.large-4.skinny-head - %span{"bo-if" => "producer.is_distributor" } - %a.is_distributor{"bo-href" => "producer.path" } - %i{bo: {class: "producer.producer_icon_font"}} + %span{"ng-if" => "::producer.is_distributor" } + %a.is_distributor{"ng-href" => "{{::producer.path}}" } + %i{ng: {class: "::producer.producer_icon_font"}} %span.margin-top - %strong{"bo-text" => "producer.name"} - %span.producer-name{"bo-if" => "!producer.is_distributor" } - %i{bo: {class: "producer.producer_icon_font"}} + %strong{"ng-bind" => "::producer.name"} + %span.producer-name{"ng-if" => "::!producer.is_distributor" } + %i{ng: {class: "::producer.producer_icon_font"}} %span.margin-top - %strong{"bo-text" => "producer.name"} + %strong{"ng-bind" => "::producer.name"} .columns.small-6.medium-3.large-3 - %span.margin-top{"bo-text" => "producer.address.city"} + %span.margin-top{"ng-bind" => "::producer.address.city"} .columns.small-4.medium-3.large-4 - %span.margin-top{"bo-bind" => "producer.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::producer.address.state_name | uppercase"} .columns.small-2.medium-2.large-1.text-right %span.margin-top %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index e842568cb3..8b7fd5695e 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -12,7 +12,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "producers/filters" - .row{bindonce: true} + .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml index ba35354bf3..9e5d3b8e82 100644 --- a/app/views/shop/_messages.html.haml +++ b/app/views/shop/_messages.html.haml @@ -24,6 +24,6 @@   .row .small-12.columns - .alert-box{ "ofn-inline-alert" => true, ng: { show: "visible" } } + .alert-box.shopfront-message{ "ofn-inline-alert" => true, ng: { show: "visible" } } = current_distributor.preferred_shopfront_message.html_safe %a.close{ ng: { click: "close()" } } × diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index f2be498451..1decc0a43c 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -29,11 +29,10 @@ .small-12.medium-6.large-6.large-offset-1.columns = render partial: "shop/products/filters" - %div.pad-top{bindonce: true} - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", - "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"} + %div.pad-top + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"} = render "shop/products/summary" - %shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants", "id" => "variant-{{ product.master.id }}"} + %shop-variant{variant: 'product.master', "ng-if" => "::!product.hasVariants", "id" => "variant-{{ product.master.id }}"} %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.count_on_hand == 0}"} %product{"ng-show" => "Products.loading"} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 8671b00cbf..4dd5b3f224 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,13 +1,13 @@ .product-thumb %a{"ng-click" => "triggerProductModal()"} %i.ofn-i_057-expand - %img{"bo-src" => "product.primaryImageOrMissing", "ng-click" => "triggerProductModal()"} + %img{"ng-src" => "{{::product.primaryImageOrMissing}}", "ng-click" => "triggerProductModal()"} .row.summary .small-10.medium-10.large-11.columns.summary-header %h3 %a{"ng-click" => "triggerProductModal()"} - %span{"bo-text" => "product.name"} + %span{"ng-bind" => "::product.name"} %i.ofn-i_057-expand %small %em @@ -15,7 +15,7 @@ %span %enterprise-modal %i.ofn-i_036-producers - %span{"bo-bind" => "enterprise.name"} + %span{"ng-bind" => "::enterprise.name"} .small-2.medium-2.large-1.columns.text-center .taxon-flag %render-svg{path: "{{product.primary_taxon.icon}}"} diff --git a/app/views/shopping_shared/_about.html.haml b/app/views/shopping_shared/_about.html.haml index ffe66c6c82..eac2fe5658 100644 --- a/app/views/shopping_shared/_about.html.haml +++ b/app/views/shopping_shared/_about.html.haml @@ -1,8 +1,8 @@ -.content#about{"ng-controller" => "AboutUsCtrl", bindonce: true} +.content#about{"ng-controller" => "AboutUsCtrl"} .panel .row .small-12.large-8.columns - %img.hero-img-small{"bo-src" => "CurrentHub.hub.promo_image", "bo-if" => "CurrentHub.hub.promo_image"} - %p{"bo-html" => "CurrentHub.hub.long_description"} + %img.hero-img-small{"ng-src" => "{{::CurrentHub.hub.promo_image}}", "ng-if" => "::CurrentHub.hub.promo_image"} + %p{"ng-bind-html" => "::CurrentHub.hub.long_description"} .small-12.large-4.columns   diff --git a/app/views/shopping_shared/_contact.html.haml b/app/views/shopping_shared/_contact.html.haml index 100d64d958..405c958768 100644 --- a/app/views/shopping_shared/_contact.html.haml +++ b/app/views/shopping_shared/_contact.html.haml @@ -57,6 +57,6 @@ - unless current_distributor.instagram.blank? %span - %a{href: "http://instagram.com.#{current_distributor.instagram}", target: "_blank" } + %a{href: "http://instagram.com/#{current_distributor.instagram}", target: "_blank" } %i.ofn-i_043-instagram / = current_distributor.instagram diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml index 8d0d9e021d..3bcd2aa37e 100644 --- a/app/views/shopping_shared/_tabs.html.haml +++ b/app/views/shopping_shared/_tabs.html.haml @@ -1,16 +1,15 @@ -#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true} +#tabs{"ng-controller" => "ShoppingTabsCtrl", "ng-cloak" => true} .row - %tabset + %tabset{ 'open-on-load' => 'false' } -# Build all tabs. - for name, heading_cols in { about: [t(:shopping_tabs_about, distributor: current_distributor.name), 6], producers: [t(:label_producers),2], contact: [t(:shopping_tabs_contact),2], groups: [t(:label_groups),2]} - -# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl. - heading, cols = heading_cols %tab.columns{heading: heading, id: "tab_#{name}", - active: "active(\'#{name}\')", - select: "toggle(\'#{name}\')", + active: "tabs.#{name}.active", + select: "select(\'#{name}\')", class: "small-12 medium-#{cols}" } = render "shopping_shared/#{name}" diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 43983e87ea..520d2376a4 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -3,39 +3,43 @@ - content_for :page_title do %h1.page-title - = t "bom_page_title" + = t("admin.orders.bulk_management.page_title") %a{ 'ofn-with-tip' => t("bom_tip") } - = t "admin.whats_this" + = t("admin.whats_this") = render :partial => 'spree/admin/shared/order_sub_menu' += admin_inject_column_preferences module: 'admin.lineItems' + %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", form: "bulk_order_form" } + %save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } } + .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } - = t "start_date" + = t("admin.start_date") %br %input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' } .date_filter{ :class => "two columns" } %label{ :for => 'end_date_filter' } - = t "end_date" + = t("admin.end_date") %br %input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' } .one.column   .filter_select{ :class => "three columns" } %label{ :for => 'supplier_filter' } - = t "producer" + = t("admin.producer") %br %select{ :class => "three columns alpha", :id => 'supplier_filter', 'select2-min-search' => 5, 'ng-model' => 'supplierFilter', 'ng-options' => 's.id as s.name for s in suppliers' } .filter_select{ :class => "three columns" } %label{ :for => 'distributor_filter' } - = t "bom_hub" + = t("admin.shop") %br %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'} .filter_select{ :class => "three columns" } %label{ :for => 'order_cycle_filter' } - = t "order_cycle" + = t("admin.order_cycle") %br %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'} .filter_clear{ :class => "two columns omega" } @@ -49,7 +53,7 @@ %div.shared_resource{ :class => "four columns alpha" } %span{ :class => 'three columns alpha' } %input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'} - = t "bom_shared" + = t("admin.orders.bulk_management.shared") %div{ :class => "eight columns" } %h6{ :class => "eight columns alpha", 'ng-show' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsProduct.name + ": ALL" }} %h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.full_name }} @@ -61,32 +65,32 @@ .one.column.alpha   .two.columns %span.two.columns - = t "group_buy_unit_size" + = t("admin.orders.bulk_management.group_buy_unit_size") %span.two.columns {{ formattedValueWithUnitName( selectedUnitsProduct.group_buy_unit_size, selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "total_qtt_ordered" + = t("admin.orders.bulk_management.total_qtt_ordered") %span.two.columns {{ formattedValueWithUnitName( sumUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "max_qtt_ordered" + = t("admin.orders.bulk_management.max_qtt_ordered") %span.two.columns {{ formattedValueWithUnitName( sumMaxUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "current_fulfilled_units" + = t("admin.orders.bulk_management.current_fulfilled_units") %span.two.columns {{ fulfilled(sumUnitValues()) }} .one.column   .two.columns %span.two.columns - = t "max_fulfilled_units" + = t("admin.orders.bulk_management.max_fulfilled_units") %span.two.columns {{ fulfilled(sumMaxUnitValues()) }} .one.column.omega   %div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' } %span{ :class => "eight columns alpha", style: 'color:red' } - = t "bulk_management_warning" + = t("admin.orders.bulk_management.variants_without_unit_value") %hr.divider.sixteen.columns.alpha.omega @@ -95,16 +99,16 @@ %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } = render 'admin/shared/bulk_actions_dropdown' %div.seven.columns   - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } %div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 - =t "bom_loading" + = t("admin.orders.bulk_management.loading") %div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'} %h1#no_results - = t "bom_no_results" + = t("admin.orders.bulk_management.no_results") .margin-bottom-50{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' } %form{ name: 'bulk_order_form' } @@ -115,42 +119,42 @@ %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.order_no{ 'ng-show' => 'columns.order_no.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } - = t "order_no" + = t("admin.orders.bulk_management.order_no") %th.full_name{ 'ng-show' => 'columns.full_name.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } - = t "name" + = t("admin.name") %th.email{ 'ng-show' => 'columns.email.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } - = t "email" + = t("admin.email") %th.phone{ 'ng-show' => 'columns.phone.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } - = t "phone" + = t("admin.phone") %th.date{ 'ng-show' => 'columns.order_date.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } - =t "bom_date" + = t("admin.orders.bulk_management.order_date") %th.producer{ 'ng-show' => 'columns.producer.visible' } %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } - = t "producer" + = t("admin.producer") %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } - = t "bom_cycle" + = t("admin.order_cycle") %th.hub{ 'ng-show' => 'columns.hub.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } - = t "bom_hub" + = t("admin.shop") %th.variant{ 'ng-show' => 'columns.variant.visible' } %a{ :href => '', 'ng-click' => "predicate = 'units_variant.full_name'; reverse = !reverse" } - = t "bom_variant" + = t("admin.orders.bulk_management.product_unit") %th.quantity{ 'ng-show' => 'columns.quantity.visible' } - = t "products_quantity" + = t("admin.quantity") %th.max{ 'ng-show' => 'columns.max.visible' } - = t "shop_variant_quantity_max" + = t("admin.orders.bulk_management.max") %th.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' } - = t "weight_volume" + = t("admin.orders.bulk_management.weight_volume") %th.price{ 'ng-show' => 'columns.price.visible' } - = t "products_price" + = t("admin.price") %th.actions %th.actions - = t "ask" + = t("admin.orders.bulk_management.ask") %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml index 7ea29bdb4c..40f135d62e 100644 --- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -3,4 +3,4 @@ %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} .nine.columns = render 'spree/admin/shared/status_message' - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml index 3624421870..a17f725b11 100644 --- a/app/views/spree/admin/products/bulk_edit/_data.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -2,3 +2,4 @@ = admin_inject_taxons = admin_inject_tax_categories = admin_inject_spree_api_key += admin_inject_column_preferences diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 7767b8de89..50789a2f82 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -21,18 +21,18 @@ %th.left-actions %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' } Expand All - %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer - %th.sku{ 'ng-show' => 'columns.sku.visible' } SKU - %th.name{ 'ng-show' => 'columns.name.visible' } Name - %th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value - %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As - %th.price{ 'ng-show' => 'columns.price.visible' } Price - %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand - %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand - %th.category{ 'ng-show' => 'columns.category.visible' } Category - %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category - %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties? - %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On + %th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer') + %th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku') + %th.name{ 'ng-show' => 'columns.name.visible' }=t('admin.name') + %th.unit{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.unit') + %th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.display_as') + %th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price') + %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand') + %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?') + %th.category{ 'ng-show' => 'columns.category.visible' }=t('admin.products.bulk_edit.category') + %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('admin.products.bulk_edit.tax_category') + %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('admin.products.bulk_edit.inherits_proerties?') + %th.available_on{ 'ng-show' => 'columns.available_on.visible' }=t('admin.products.bulk_edit.av_on') %th.actions %th.actions %th.actions diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 6ac25ae286..01af8014d1 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -18,8 +18,8 @@ %td.price{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } - %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } - %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } + %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } + %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' } %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } %td.category{ 'ng-if' => 'columns.category.visible' } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index fb68704b79..f2e65d6c88 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -14,8 +14,8 @@ %td{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } %td{ 'ng-show' => 'columns.on_hand.visible' } - %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } - %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } + %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } + %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' } %td{ 'ng-show' => 'columns.on_demand.visible' } %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } %td{ 'ng-show' => 'columns.category.visible' } diff --git a/app/views/spree/admin/shared/_trial_progress_bar.html.haml b/app/views/spree/admin/shared/_trial_progress_bar.html.haml index 3deef097fe..4c76e40705 100644 --- a/app/views/spree/admin/shared/_trial_progress_bar.html.haml +++ b/app/views/spree/admin/shared/_trial_progress_bar.html.haml @@ -1,7 +1,7 @@ - if enterprise -if shop_trial_in_progress?(enterprise) #trial_progress_bar - = t(:shop_trial_in_progress, days: remaining_trial_days(enterprise)) + = "#{t(:shop_trial_expires_in)} #{remaining_trial_days(enterprise)}." -elsif shop_trial_expired?(enterprise) #trial_progress_bar - = t(:shop_trial_expired) + = t(:shop_trial_expired_notice) diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml index 5c87077b4c..4ce051e5b8 100644 --- a/app/views/spree/users/_fat.html.haml +++ b/app/views/spree/users/_fat.html.haml @@ -12,20 +12,20 @@ %tbody.transaction-group{"ng-repeat" => "order in distributor.distributed_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"} %tr.order-row %td.order1 - %a{"bo-href" => "order.path", "bo-text" => "('order' | t )+ ' ' + order.number"} - %td.order2{"bo-text" => "order.completed_at"} - %td.order3.show-for-large-up{"bo-text" => "'spree.payment_states.' + order.payment_state | t | capitalize"} - %td.order4.show-for-large-up{"bo-text" => "'spree.shipment_states.' + order.shipment_state | t | capitalize"} - %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","bo-text" => "order.total | localizeCurrency"} - %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "bo-text" => "order.outstanding_balance | localizeCurrency"} - %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "bo-text" => "order.running_balance | localizeCurrency"} - %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} - %td.order1{"bo-text" => "payment.payment_method"} - %td.order2{"bo-text" => "payment.updated_at"} - %td.order3.show-for-large-up - %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"} - %span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} - %td.order4.show-for-large-up - %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"} - %td.order6.show-for-large-up - %td.order7 + %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::('order' | t )+ ' ' + order.number"} + %td.order2{"ng-bind" => "::order.completed_at"} + %td.order3.show-for-large-up{"ng-bind" => "::'spree.payment_states.' + order.payment_state | t | capitalize"} + %td.order4.show-for-large-up{"ng-bind" => "::'spree.shipment_states.' + order.shipment_state | t | capitalize"} + %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"} + %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "ng-bind" => "::order.outstanding_balance | localizeCurrency"} + %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "ng-bind" => "::order.running_balance | localizeCurrency"} + %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} + %td.order1{"ng-bind" => "::payment.payment_method"} + %td.order2{"ng-bind" => "::payment.updated_at"} + %td.order3.show-for-large-up + %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"} + %span{"ng-bind" => "::'spree.payment_states.' + payment.state | t | capitalize"} + %td.order4.show-for-large-up + %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","ng-bind" => "::payment.amount | localizeCurrency"} + %td.order6.show-for-large-up + %td.order7 diff --git a/app/views/spree/users/_skinny.html.haml b/app/views/spree/users/_skinny.html.haml index 14c04f024a..4894824296 100644 --- a/app/views/spree/users/_skinny.html.haml +++ b/app/views/spree/users/_skinny.html.haml @@ -4,9 +4,9 @@ %img.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"} .columns.small-10.medium-5 %span.margin-top - %strong{"bo-text" => "distributor.name"} + %strong{"ng-bind" => "::distributor.name"} .columns.small-8.small-offset-2.medium-3.text-right - %span.margin-top.distributor-balance{"bo-text" => "distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" } + %span.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" } .columns.small-2.medium-2.text-right %span.margin-top %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml index 83171daf0d..0b4800f79e 100644 --- a/app/views/spree/users/show.html.haml +++ b/app/views/spree/users/show.html.haml @@ -9,7 +9,7 @@ (#{link_to t(:edit), spree.edit_account_path}) %h3= t(:my_orders) .orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true} - .row{bindonce: true} + .row .small-12.columns .active_table %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor", @@ -19,6 +19,6 @@ .small-12.columns = render partial: "spree/users/skinny" = render partial: "spree/users/fat" - .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "bo-text" => "'you_have_no_orders_yet' | t"} + .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"} = render partial: "shared/footer" diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 2eae5eb8a1..7de5db0ad4 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,6 +1,7 @@ # Localization file for British English. Add more files in this directory for other locales. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + en-GB: activerecord: errors: @@ -58,6 +59,13 @@ en-GB: sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" + # To customise text in emails. + producer_mail_greeting: "Dear" + producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" + producer_mail_text_after: "Please confirm that you have got this email. Please send me an invoice for this amount so that we can send you payment. If you need to phone me on the day, please use the number below." + producer_mail_signoff: "Thanks and best wishes" admin: # General form elements @@ -614,6 +622,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_distributor: Distributor products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here. + shop_trial_length: "Shop Trial Length (Days)" + shop_trial_expires_in: "Your shopfront trial expires in" + shop_trial_expired_notice: "Open Food Network UK is currently free while we prepare for our soft launch in April, 2016." + # keys used in javascript password: Password remember_me: Remember Me @@ -1006,6 +1018,7 @@ Please follow the instructions there to make your enterprise visible on the Open validation_msg_product_category_cant_be_blank: "^Product Category cant be blank" validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" + spree: shipment_states: backorder: backorder diff --git a/config/locales/en.yml b/config/locales/en.yml index ac8d07bd7e..3249f00d5a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -71,13 +71,29 @@ en: admin: + # Common properties / models + date: Date + email: Email + name: Name + on_hand: On Hand + on_demand: On Demand + on_demand?: On Demand? + order_cycle: Order Cycle + phone: Phone + price: Price + producer: Producer + product: Product + quantity: Quantity + shop: Shop + sku: SKU + tags: Tags + variant: Variant + # General form elements quick_search: Quick Search clear_all: Clear All - producer: Producer - shop: Shop - product: Product - variant: Variant + start_date: "Start Date" + end_date: "End Date" columns: Columns actions: Actions @@ -91,40 +107,79 @@ en: customers: index: - add_customer: "Add customer" + add_customer: "Add Customer" + new_customer: "New Customer" customer_placeholder: "customer@example.org" - inventory: - title: Inventory - description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page - sku: SKU - price: Price - on_hand: On Hand - on_demand: On Demand? - enable_reset: Enable Stock Level Reset? - inherit: Inherit? - add: Add - hide: Hide - select_a_shop: Select A Shop - review_now: Review Now - new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. - currently_empty: Your inventory is currently empty - no_matching_products: No matching products found in your inventory - no_hidden_products: No products have been hidden from this inventory - no_matching_hidden_products: No hidden products match your search criteria - no_new_products: No new products are available to add to this inventory - no_matching_new_products: No new products match your search criteria - inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. - hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. - new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + valid_email_error: Please enter a valid email address + add_a_new_customer_for: Add a new customer for %{shop_name} + code: Code + products: + bulk_edit: + unit: Unit + display_as: Display As + category: Category + tax_category: Tax Category + inherits_properties?: Inherits Properties? + available_on: Available On + av_on: "Av. On" - order_cycle: - choose_products_from: "Choose Products From:" + variant_overrides: + index: + title: Inventory + description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page + enable_reset?: Enable Stock Reset? + inherit?: Inherit? + add: Add + hide: Hide + select_a_shop: Select A Shop + review_now: Review Now + new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. + currently_empty: Your inventory is currently empty + no_matching_products: No matching products found in your inventory + no_hidden_products: No products have been hidden from this inventory + no_matching_hidden_products: No hidden products match your search criteria + no_new_products: No new products are available to add to this inventory + no_matching_new_products: No new products match your search criteria + inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. + hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. + new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + + orders: + bulk_management: + tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + shared: "Shared Resource?" + order_no: "Order No." + order_date: "Order Date" + max: "Max" + product_unit: "Product: Unit" + weight_volume: "Weight/Volume" + ask: "Ask?" + page_title: "Bulk Order Management" + actions_delete: "Delete Selected" + loading: "Loading orders" + no_results: "No orders found." + group_buy_unit_size: "Group Buy Unit Size" + total_qtt_ordered: "Total Quantity Ordered" + max_qtt_ordered: "Max Quantity Ordered" + current_fulfilled_units: "Current Fulfilled Units" + max_fulfilled_units: "Max Fulfilled Units" + order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." + variants_without_unit_value: "WARNING: Some variants do not have a unit value" + + order_cycles: + edit: + choose_products_from: "Choose Products From:" enterprise: select_outgoing_oc_products_from: Select outgoing OC products from enterprises: + index: + producer?: Producer? + package: Package + status: Status + manage: Manage form: primary_details: shopfront_requires_login: "Shopfront requires login?" @@ -329,22 +384,6 @@ en: order_payment_paypal_successful: Your payment via PayPal has been processed successfully. order_hub_info: Hub Info - bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." - bom_shared: "Shared Resource?" - bom_page_title: "Bulk Order Management" - bom_no: "Order no." - bom_date: "Order date" - bom_cycle: "Order cycle" - bom_max: "Max" - bom_hub: "Hub" - bom_variant: "Product: Unit" - bom_final_weigth_volume: "Weight/Volume" - bom_quantity: "Quantity" - bom_actions_delete: "Delete Selected" - bom_loading: "Loading orders" - bom_no_results: "No orders found." - bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." - unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." unsaved_changes_error: "Fields with red borders contain errors." @@ -410,6 +449,13 @@ See the %{link} to find out more about %{sitename}'s features and to start using If you are a producer or food enterprise, we are excited to have you as a part of the network." email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + producer_mail_greeting: "Dear" + producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" + producer_mail_text_after: "" + producer_mail_signoff: "Thanks and best wishes" + shopping_oc_closed: Orders are closed shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" @@ -564,20 +610,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_description: Description products_variant: Variant products_quantity: Quantity - products_availabel: Available? + products_available: Available? products_producer: "Producer" products_price: "Price" - products_sku: "SKU" - products_name: "name" - products_unit: "unit" - products_on_hand: "on hand" - products_on_demand: "On demand?" - products_category: "Category" - products_tax_category: "tax category" - products_available_on: "Available On" - products_inherit: "Inherit?" - products_inherits_properties: "Inherits Properties?" - products_stock_level_reset: "Enable Stock Level Reset?" register_title: Register @@ -627,6 +662,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_distributor: Distributor products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here. + shop_trial_length: "Shop Trial Length (Days)" + shop_trial_expires_in: "Your shopfront trial expires in" + shop_trial_expired_notice: "Good news! We have decided to extend shopfront trials until further notice." + + # keys used in javascript password: Password remember_me: Remember Me @@ -857,7 +897,7 @@ Please follow the instructions there to make your enterprise visible on the Open calculator: "Calculator" calculator_values: "Calculator values" new_order_cycles: "New Order Cycles" - select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle" + select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" edit_order_cycle: "Edit Order Cycle" roles: "Roles" update: "Update" @@ -930,19 +970,7 @@ Please follow the instructions there to make your enterprise visible on the Open manage_products: "Manage products" edit_profile_details: "Edit profile details" edit_profile_details_etc: "Change your profile description, images, etc." - start_date: "Start Date" - end_date: "End Date" order_cycle: "Order Cycle" - group_buy_unit_size: "Group Buy Unit Size" - total_qtt_ordered: "Total Quantity Ordered" - max_qtt_ordered: "Max Quantity Ordered" - current_fulfilled_units: "Current Fulfilled Units" - max_fulfilled_units: "Max Fulfilled Units" - bulk_management_warning: "WARNING: Some variants do not have a unit value" - ask: "Ask?" - no_orders_found: "No orders found." - order_no: "Order No." - weight_volume: "Weight/Volume" remove_tax: "Remove tax" tax_settings: "Tax Settings" products_require_tax_category: "products require tax category" diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index 0cc4fc385e..20ec36622c 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -9,7 +9,6 @@ module.exports = function(config) { 'app/assets/javascripts/shared/jquery-1.8.0.js', // TODO: Can we link to Rails' jquery? 'app/assets/javascripts/shared/jquery.timeago.js', 'app/assets/javascripts/shared/angular-local-storage.js', - 'app/assets/javascripts/shared/bindonce.min.js', 'app/assets/javascripts/shared/ng-infinite-scroll.min.js', 'app/assets/javascripts/shared/angular-slideables.js', diff --git a/config/routes.rb b/config/routes.rb index e240f901a8..f1b96de181 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,7 +117,9 @@ Openfoodnetwork::Application.routes.draw do resources :customers, only: [:index, :create, :update, :destroy] - resources :tags, only: [:index], format: :json + resources :tag_rules, only: [], format: :json do + get :map_by_tag, on: :collection + end resource :content @@ -133,6 +135,10 @@ Openfoodnetwork::Application.routes.draw do resource :cache_settings resource :account, only: [:show], controller: 'account' + + resources :column_preferences, only: [], format: :json do + put :bulk_update, on: :collection + end end namespace :api do diff --git a/db/migrate/20160116024333_create_column_preferences.rb b/db/migrate/20160116024333_create_column_preferences.rb new file mode 100644 index 0000000000..e38c51d858 --- /dev/null +++ b/db/migrate/20160116024333_create_column_preferences.rb @@ -0,0 +1,13 @@ +class CreateColumnPreferences < ActiveRecord::Migration + def change + create_table :column_preferences do |t| + t.references :user, null: false, index: true + t.string :action_name, null: false, index: true + t.string :column_name, null: false + t.boolean :visible, null: false + + t.timestamps + end + add_index :column_preferences, [:user_id, :action_name, :column_name], unique: true, name: 'index_column_prefs_on_user_id_and_action_name_and_column_name' + end +end diff --git a/db/schema.rb b/db/schema.rb index dc54bd8f3d..60b72adeea 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -176,6 +176,17 @@ ActiveRecord::Schema.define(:version => 20160401043927) do add_index "cms_snippets", ["site_id", "identifier"], :name => "index_cms_snippets_on_site_id_and_identifier", :unique => true add_index "cms_snippets", ["site_id", "position"], :name => "index_cms_snippets_on_site_id_and_position" + create_table "column_preferences", :force => true do |t| + t.integer "user_id", :null => false + t.string "action_name", :null => false + t.string "column_name", :null => false + t.boolean "visible", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "column_preferences", ["user_id", "action_name", "column_name"], :name => "index_column_prefs_on_user_id_and_action_name_and_column_name", :unique => true + create_table "coordinator_fees", :force => true do |t| t.integer "order_cycle_id" t.integer "enterprise_fee_id" @@ -683,9 +694,9 @@ ActiveRecord::Schema.define(:version => 20160401043927) do t.string "email" t.text "special_instructions" t.integer "distributor_id" + t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" - t.integer "order_cycle_id" t.integer "cart_id" t.integer "customer_id" end diff --git a/lib/open_food_network/bill_calculator.rb b/lib/open_food_network/bill_calculator.rb index 84457d6530..90f5077a14 100644 --- a/lib/open_food_network/bill_calculator.rb +++ b/lib/open_food_network/bill_calculator.rb @@ -1,6 +1,6 @@ module OpenFoodNetwork class BillCalculator - attr_accessor :turnover, :fixed, :rate, :cap, :tax_rate + attr_accessor :turnover, :fixed, :rate, :cap, :tax_rate, :minimum_billable_turnover def initialize(opts={}) @turnover = opts[:turnover] || 0 @@ -8,11 +8,13 @@ module OpenFoodNetwork @rate = opts[:rate] || Spree::Config[:account_invoices_monthly_rate] @cap = opts[:cap] || Spree::Config[:account_invoices_monthly_cap] @tax_rate = opts[:tax_rate] || Spree::Config[:account_invoices_tax_rate] + @minimum_billable_turnover = opts[:minimum_billable_turnover] || Spree::Config[:minimum_billable_turnover] end def bill + return 0 if turnover < minimum_billable_turnover bill = fixed + (turnover * rate) - bill = cap > 0 ? [bill, cap].min : bill + bill = [bill, cap].min if cap > 0 bill * (1 + tax_rate) end end diff --git a/lib/open_food_network/business_model_configuration_validator.rb b/lib/open_food_network/business_model_configuration_validator.rb index d83d94ffc1..93534937db 100644 --- a/lib/open_food_network/business_model_configuration_validator.rb +++ b/lib/open_food_network/business_model_configuration_validator.rb @@ -5,12 +5,14 @@ module OpenFoodNetwork class BusinessModelConfigurationValidator include ActiveModel::Validations - attr_accessor :account_invoices_monthly_fixed, :account_invoices_monthly_rate, :account_invoices_monthly_cap, :account_invoices_tax_rate + attr_accessor :shop_trial_length_days, :account_invoices_monthly_fixed, :account_invoices_monthly_rate, :account_invoices_monthly_cap, :account_invoices_tax_rate, :minimum_billable_turnover + validates :shop_trial_length_days, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :account_invoices_monthly_fixed, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :account_invoices_monthly_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 } validates :account_invoices_monthly_cap, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :account_invoices_tax_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 } + validates :minimum_billable_turnover, presence: true, numericality: { greater_than_or_equal_to: 0 } def initialize(attr, button=nil) attr.each { |k,v| instance_variable_set("@#{k}", v) } diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb new file mode 100644 index 0000000000..c18ac79550 --- /dev/null +++ b/lib/open_food_network/column_preference_defaults.rb @@ -0,0 +1,82 @@ +module OpenFoodNetwork + module ColumnPreferenceDefaults + + private + + # NOTE: These methods define valid column names (via hash keys) + # as well as default values for column attributes (eg. visiblity) + # Default values can be overridden by storing a different value + # for a given user, action_name and column_name + + def variant_overrides_index_columns + node = 'admin.variant_overrides.index' + { + producer: { name: I18n.t("admin.producer"), visible: true }, + product: { name: I18n.t("admin.product"), visible: true }, + sku: { name: I18n.t("admin.sku"), visible: false }, + price: { name: I18n.t("admin.price"), visible: true }, + on_hand: { name: I18n.t("admin.on_hand"), visible: true }, + on_demand: { name: I18n.t("admin.on_demand?"), visible: false }, + reset: { name: I18n.t("#{node}.enable_reset?"), visible: false }, + inheritance: { name: I18n.t("#{node}.inherit?"), visible: false }, + visibility: { name: I18n.t("#{node}.hide"), visible: false } + } + end + + def customers_index_columns + node = 'admin.customers.index' + { + email: { name: I18n.t("admin.email"), visible: true }, + code: { name: I18n.t("#{node}.code"), visible: true }, + tags: { name: I18n.t("admin.tags"), visible: true } + } + end + + def orders_bulk_management_columns + node = "admin.orders.bulk_management" + { + order_no: { name: I18n.t("#{node}.order_no"), visible: false }, + full_name: { name: I18n.t("admin.name"), visible: true }, + email: { name: I18n.t("admin.email"), visible: false }, + phone: { name: I18n.t("admin.phone"), visible: false }, + order_date: { name: I18n.t("#{node}.order_date"), visible: true }, + producer: { name: I18n.t("admin.producer"), visible: true }, + order_cycle: { name: I18n.t("admin.order_cycle"), visible: false }, + hub: { name: I18n.t("admin.shop"), visible: false }, + variant: { name: I18n.t("#{node}.product_unit"), visible: true }, + quantity: { name: I18n.t("admin.quantity"), visible: true }, + max: { name: I18n.t("#{node}.max"), visible: true }, + final_weight_volume: { name: I18n.t("#{node}.weight_volume"), visible: false }, + price: { name: I18n.t("admin.price"), visible: false } + } + end + + def products_bulk_edit_columns + node = "admin.products.bulk_edit" + { + producer: { name: I18n.t("admin.producer"), visible: true }, + sku: { name: I18n.t("admin.sku"), visible: false }, + name: { name: I18n.t("admin.name"), visible: true }, + unit: { name: I18n.t("#{node}.unit"), visible: true }, + price: { name: I18n.t("admin.price"), visible: true }, + on_hand: { name: I18n.t("admin.on_hand"), visible: true }, + on_demand: { name: I18n.t("admin.on_demand"), visible: false }, + category: { name: I18n.t("#{node}.category"), visible: false }, + tax_category: { name: I18n.t("#{node}.tax_category"), visible: false }, + inherits_properties: { name: I18n.t("#{node}.inherits_properties?"), visible: false }, + available_on: { name: I18n.t("#{node}.available_on"), visible: false } + } + end + + def enterprises_index_columns + node = "admin.enterprises.index" + { + name: { name: I18n.t("admin.name"), visible: true }, + producer: { name: I18n.t("#{node}.producer?"), visible: true }, + package: { name: I18n.t("#{node}.package"), visible: true }, + status: { name: I18n.t("#{node}.status"), visible: true }, + manage: { name: I18n.t("#{node}.manage"), visible: true } + } + end + end +end diff --git a/package.json b/package.json new file mode 100644 index 0000000000..bb174e883d --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "openfoodnetwork", + "version": "1.7.1", + "repository": { + "type": "git", + "url": "https://github.com/openfoodfoundation/openfoodnetwork" + }, + "devDependencies": { + "phantomjs-prebuilt": "~2.1.7", + "karma": "~0.13.22", + "karma-jasmine": "~0.3.8", + "jasmine-core": "~2.4.1", + "karma-phantomjs-launcher": "~1.0.0", + "karma-coffee-preprocessor": "~0.3.0" + }, + "license": "AGPL-1.0" +} diff --git a/spec/controllers/admin/business_model_configuration_controller_spec.rb b/spec/controllers/admin/business_model_configuration_controller_spec.rb index b0ae86d4ac..3836773b08 100644 --- a/spec/controllers/admin/business_model_configuration_controller_spec.rb +++ b/spec/controllers/admin/business_model_configuration_controller_spec.rb @@ -9,7 +9,9 @@ describe Admin::BusinessModelConfigurationController, type: :controller do account_invoices_monthly_fixed: 5, account_invoices_monthly_rate: 0.02, account_invoices_monthly_cap: 50, - account_invoices_tax_rate: 0.1 + account_invoices_tax_rate: 0.1, + shop_trial_length_days: 30, + minimum_billable_turnover: 0 }) end @@ -53,16 +55,20 @@ describe Admin::BusinessModelConfigurationController, type: :controller do params[:settings][:account_invoices_monthly_rate] = '2' params[:settings][:account_invoices_monthly_cap] = '-1' params[:settings][:account_invoices_tax_rate] = '4' + params[:settings][:shop_trial_length_days] = '-30' + params[:settings][:minimum_billable_turnover] = '-1' spree_get :update, params end it "does not allow them to be set" do expect(response).to render_template :edit - expect(assigns(:settings).errors.count).to be 5 + expect(assigns(:settings).errors.count).to be 7 expect(Spree::Config.account_invoices_monthly_fixed).to eq 5 expect(Spree::Config.account_invoices_monthly_rate).to eq 0.02 expect(Spree::Config.account_invoices_monthly_cap).to eq 50 expect(Spree::Config.account_invoices_tax_rate).to eq 0.1 + expect(Spree::Config.shop_trial_length_days).to eq 30 + expect(Spree::Config.minimum_billable_turnover).to eq 0 end end @@ -72,6 +78,8 @@ describe Admin::BusinessModelConfigurationController, type: :controller do params[:settings][:account_invoices_monthly_rate] = '0.05' params[:settings][:account_invoices_monthly_cap] = '30' params[:settings][:account_invoices_tax_rate] = '0.15' + params[:settings][:shop_trial_length_days] = '20' + params[:settings][:minimum_billable_turnover] = '10' end it "sets global config to the specified values" do @@ -81,6 +89,8 @@ describe Admin::BusinessModelConfigurationController, type: :controller do expect(Spree::Config.account_invoices_monthly_rate).to eq 0.05 expect(Spree::Config.account_invoices_monthly_cap).to eq 30 expect(Spree::Config.account_invoices_tax_rate).to eq 0.15 + expect(Spree::Config.shop_trial_length_days).to eq 20 + expect(Spree::Config.minimum_billable_turnover).to eq 10 end end end diff --git a/spec/controllers/admin/column_preferences_controller_spec.rb b/spec/controllers/admin/column_preferences_controller_spec.rb new file mode 100644 index 0000000000..19d867d9b8 --- /dev/null +++ b/spec/controllers/admin/column_preferences_controller_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Admin::ColumnPreferencesController, type: :controller do + include AuthenticationWorkflow + + + describe "bulk_update" do + let!(:user1) { create(:user) } + let!(:user2) { create(:user) } + let!(:enterprise) { create(:enterprise, owner: user1, users: [user1, user2]) } + + context "json" do + let!(:column_preference) { ColumnPreference.create(user_id: user1.id, action_name: 'enterprises_index', column_name: "name", visible: true) } + + let(:column_preference_params) { [ + { id: column_preference.id, user_id: user1.id, action_name: "enterprises_index", column_name: 'name', visible: false }, + { id: nil, user_id: user1.id, action_name: "enterprises_index", column_name: 'producer', visible: true }, + { id: nil, user_id: user1.id, action_name: "enterprises_index", column_name: 'status', visible: true } + ] } + + context "where I don't own the preferences submitted" do + before do + allow(controller).to receive(:spree_current_user) { user2 } + end + + it "prevents me from updating the column preferences" do + spree_put :bulk_update, format: :json, action_name: "enterprises_index", column_preferences: column_preference_params + expect(ColumnPreference.count).to be 1 + end + end + + context "where I own the preferences submitted" do + before do + allow(controller).to receive(:spree_current_user) { user1 } + end + + it "allows me to update the column preferences" do + spree_put :bulk_update, format: :json, action_name: "enterprises_index", column_preferences: column_preference_params + expect(ColumnPreference.where(user_id: user1.id, action_name: 'enterprises_index').count).to be 3 + end + end + end + end +end diff --git a/spec/features/admin/authentication_spec.rb b/spec/features/admin/authentication_spec.rb index 8473551c99..fe2eb156c5 100644 --- a/spec/features/admin/authentication_spec.rb +++ b/spec/features/admin/authentication_spec.rb @@ -16,14 +16,14 @@ feature "Authentication", js: true do fill_in "Email", with: user.email fill_in "Password", with: user.password click_login_button - page.should have_content "DASHBOARD" - current_path.should == spree.admin_path + expect(page).to have_content "DASHBOARD" + expect(page).to have_current_path spree.admin_path end end scenario "viewing my account" do login_to_admin_section click_link "Account" - current_path.should == spree.account_path + expect(page).to have_current_path spree.account_path end end diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 0b23e77f6c..ef4fc13e39 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -18,12 +18,12 @@ feature %q{ end context "displaying the list of line items" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'address', completed_at: nil ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'address', completed_at: nil ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:li3) { create(:line_item, order: o3 ) } before :each do visit '/admin/orders/bulk_management' @@ -32,15 +32,15 @@ feature %q{ it "displays a list of line items" do expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" - expect(page).to_not have_selector "tr#li_#{li3.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" end end context "displaying individual columns" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: FactoryGirl.create(:address) ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: nil ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: FactoryGirl.create(:product_with_option_types) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: create(:address) ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: nil ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2, product: create(:product_with_option_types) ) } before :each do visit '/admin/orders/bulk_management' @@ -90,8 +90,8 @@ feature %q{ end context "tracking changes" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 5 ) } before :each do visit '/admin/orders/bulk_management' @@ -105,8 +105,8 @@ feature %q{ end context "submitting data to the server" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 5 ) } before :each do Spree::Config.set(allow_backorders: false) @@ -116,19 +116,19 @@ feature %q{ context "when acceptable data is sent to the server" do it "displays an update button which submits pending changes" do - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" fill_in "quantity", :with => 2 expect(page).to have_selector "input[name='quantity'].ng-dirty" expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" expect(page).to have_selector "#save-bar", text: "All changes saved" - expect(page).to_not have_selector "input[name='quantity'].ng-dirty" + expect(page).to have_no_selector "input[name='quantity'].ng-dirty" end end context "when unacceptable data is sent to the server" do it "displays an update button which submits pending changes" do - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" fill_in "quantity", :with => li1.variant.on_hand + li1.quantity + 10 expect(page).to have_selector "input[name='quantity'].ng-dirty" expect(page).to have_selector "#save-bar", text: "You have unsaved changes" @@ -146,28 +146,28 @@ feature %q{ admin_user = quick_login_as_admin end - let!(:p1) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:p1) { create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [create(:variant, unit_value: 1000)] ) } let!(:v1) { p1.variants.first } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, variant: v1, :quantity => 5, :final_weight_volume => 1000, price: 10.00 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, variant: v1, :quantity => 5, :final_weight_volume => 1000, price: 10.00 ) } before { v1.update_attribute(:on_hand, 100)} context "modifying the weight/volume of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns-dropdown div.menu div.menu_item", text: "Price").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + find("div#columns-dropdown div.menu div.menu_item", text: "Price").click # hide dropdown - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$50.00" fill_in "final_weight_volume", :with => 2000 expect(page).to have_field "price", with: "$100.00" end click_button "Save Changes" - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" li1.reload expect(li1.final_weight_volume).to eq 2000 expect(li1.price).to eq 20.00 @@ -177,9 +177,9 @@ feature %q{ context "modifying the quantity of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Price").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Price").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}" fill_in "quantity", :with => 6 @@ -191,9 +191,9 @@ feature %q{ context "modifying the quantity of a line item" do it "weight/volume is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "final_weight_volume", with: "#{li1.final_weight_volume.round}" fill_in "quantity", :with => 6 @@ -213,11 +213,11 @@ feature %q{ expect(page).to have_selector "th", :text => "QUANTITY" expect(page).to have_selector "th", :text => "MAX" - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Producer").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Producer").click + find("div#columns-dropdown", :text => "COLUMNS").click - expect(page).to_not have_selector "th", :text => "PRODUCER" + expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "ORDER DATE" expect(page).to have_selector "th", :text => "PRODUCT: UNIT" @@ -230,60 +230,64 @@ feature %q{ context "supplier filter" do let!(:s1) { create(:supplier_enterprise) } let!(:s2) { create(:supplier_enterprise) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: create(:simple_order_cycle) ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s2) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: create(:simple_order_cycle) ) } + let!(:li1) { create(:line_item, order: o1, product: create(:product, supplier: s1) ) } + let!(:li2) { create(:line_item, order: o1, product: create(:product, supplier: s2) ) } before :each do visit '/admin/orders/bulk_management' end it "displays a select box for producers, which filters line items by the selected supplier" do - supplier_names = ["All"] - Enterprise.is_primary_producer.each{ |e| supplier_names << e.name } + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" open_select2 "div.select2-container#s2id_supplier_filter" - supplier_names.each { |sn| expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: sn } + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: "All" + Enterprise.is_primary_producer.map(&:name).each do |sn| + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: sn + end close_select2 "div.select2-container#s2id_supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from supplier filter" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end end context "distributor filter" do let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: create(:simple_order_cycle) ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: create(:simple_order_cycle) ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: create(:simple_order_cycle) ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: create(:simple_order_cycle) ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' end it "displays a select box for distributors, which filters line items by the selected distributor" do - distributor_names = ["All"] - Enterprise.is_distributor.each{ |e| distributor_names << e.name } + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" open_select2 "div.select2-container#s2id_distributor_filter" - distributor_names.each { |dn| expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: dn } + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: "All" + Enterprise.is_distributor.map(&:name).each do |dn| + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: dn + end close_select2 "div.select2-container#s2id_distributor_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true select2_select d1.name, from: "distributor_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from distributor filter" do @@ -291,7 +295,7 @@ feature %q{ expect(page).to have_selector "tr#li_#{li2.id}" select2_select d1.name, from: "distributor_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "distributor_filter" expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" @@ -300,25 +304,25 @@ feature %q{ context "order_cycle filter" do let!(:distributor) { create(:distributor_enterprise) } - let!(:oc1) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc2 ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:oc1) { create(:simple_order_cycle, distributors: [distributor]) } + let!(:oc2) { create(:simple_order_cycle, distributors: [distributor]) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc2 ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before do visit '/admin/orders/bulk_management' end it "displays a select box for order cycles, which filters line items by the selected order cycle" do - expect(page).to have_select2 'order_cycle_filter', with_options: OrderCycle.pluck(:name).unshift("All") expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_select2 'order_cycle_filter', with_options: OrderCycle.pluck(:name).unshift("All") select2_select oc1.name, from: "order_cycle_filter" - expect(page).to_not have_selector "#loading img.spinner" + expect(page).to have_no_selector "#loading img.spinner" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from order_cycle filter" do @@ -326,7 +330,7 @@ feature %q{ expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "order_cycle_filter" expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" @@ -338,42 +342,46 @@ feature %q{ let!(:s2) { create(:supplier_enterprise) } let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } - let!(:oc1) { FactoryGirl.create(:simple_order_cycle, suppliers: [s1], distributors: [d1] ) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle, suppliers: [s2], distributors: [d2] ) } - let!(:p1) { FactoryGirl.create(:product, supplier: s1) } - let!(:p2) { FactoryGirl.create(:product, supplier: s2) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: oc2 ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: p1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: p2 ) } + let!(:oc1) { create(:simple_order_cycle, suppliers: [s1], distributors: [d1] ) } + let!(:oc2) { create(:simple_order_cycle, suppliers: [s2], distributors: [d2] ) } + let!(:p1) { create(:product, supplier: s1) } + let!(:p2) { create(:product, supplier: s2) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: oc1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: oc2 ) } + let!(:li1) { create(:line_item, order: o1, product: p1 ) } + let!(:li2) { create(:line_item, order: o2, product: p2 ) } before :each do visit '/admin/orders/bulk_management' end it "allows filters to be used in combination" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select d1.name, from: "distributor_filter" select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select d2.name, from: "distributor_filter" select2_select s2.name, from: "supplier_filter" - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select oc2.name, from: "order_cycle_filter" - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end it "displays a 'Clear All' button which sets all select filters to 'All'" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" select2_select d1.name, from: "distributor_filter" select2_select s1.name, from: "supplier_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" expect(page).to have_button "Clear All" click_button "Clear All" expect(page).to have_selector "div#s2id_order_cycle_filter a.select2-choice", text: "All" @@ -386,12 +394,12 @@ feature %q{ end context "using quick search" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:li3) { create(:line_item, order: o3 ) } before :each do visit '/admin/orders/bulk_management' @@ -402,23 +410,23 @@ feature %q{ end it "filters line items based on their attributes and the contents of the quick search input" do - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li3.id}" fill_in "quick_search", :with => o1.email - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}", true + expect(page).to have_no_selector "tr#li_#{li3.id}" end end context "using date restriction controls" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.current - 8).strftime("%F %T") ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.current + 2).strftime("%F %T") ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, :quantity => 2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3, :quantity => 3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: (Date.current - 8).strftime("%F %T") ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: (Date.current + 2).strftime("%F %T") ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 1 ) } + let!(:li2) { create(:line_item, order: o2, :quantity => 2 ) } + let!(:li3) { create(:line_item, order: o3, :quantity => 3 ) } before :each do visit '/admin/orders/bulk_management' @@ -434,21 +442,21 @@ feature %q{ end it "only loads line items whose orders meet the date restriction criteria" do - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" end it "displays only line items whose orders meet the date restriction criteria, when changed" do fill_in "start_date_filter", :with => (Date.current - 9).strftime("%F") - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" fill_in "end_date_filter", :with => (Date.current + 3).strftime("%F") - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li3.id}" end context "when the form is dirty" do @@ -481,10 +489,10 @@ feature %q{ end context "bulk action controls" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -512,15 +520,15 @@ feature %q{ context "performing actions" do it "deletes selected items" do - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" within("tr#li_#{li2.id} td.bulk") do check "bulk" end find("div#bulk-actions-dropdown").click find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end end @@ -540,18 +548,18 @@ feature %q{ find("div#bulk-actions-dropdown").click find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click fill_in "quick_search", with: '' - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end end end context "using action buttons" do context "using edit buttons" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -564,27 +572,28 @@ feature %q{ page.driver.dismiss_modal :confirm, text: "Unsaved changes exist and will be lost if you continue." do within "tr#li_#{li1.id}" do fill_in "quantity", with: (li1.quantity + 1) - first("a.edit-order").click + find("a.edit-order").click end end # So we save the changes expect(URI.parse(current_url).path).to eq "/admin/orders/bulk_management" click_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "All changes saved" # And try again within "tr#li_#{li1.id}" do - first("a.edit-order").click + find("a.edit-order").click end expect(URI.parse(current_url).path).to eq "/admin/orders/#{o1.number}/edit" end end context "using delete buttons" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -592,8 +601,8 @@ feature %q{ it "removes a line item when the relevant delete button is clicked" do expect(page).to have_selector "a.delete-line-item", :count => 2 - first("a.delete-line-item").click - expect(page).to_not have_selector "a.delete-line-item", :count => 2 + find("tr#li_#{li1.id} a.delete-line-item").click + expect(page).to have_no_selector "a.delete-line-item", :count => 2 expect(page).to have_selector "a.delete-line-item", :count => 1 visit '/admin/orders/bulk_management' expect(page).to have_selector "a.delete-line-item", :count => 1 @@ -602,15 +611,15 @@ feature %q{ end context "clicking the link on variant name" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:p3) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:p3) { create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [create(:variant, unit_value: 1000)] ) } let!(:v3) { p3.variants.first } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3, variant: v3, quantity: 3, max_quantity: 6 ) } - let!(:li4) { FactoryGirl.create(:line_item, order: o2, variant: v3, quantity: 1, max_quantity: 3 ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li3) { create(:line_item, order: o3, variant: v3, quantity: 3, max_quantity: 6 ) } + let!(:li4) { create(:line_item, order: o2, variant: v3, quantity: 1, max_quantity: 3 ) } before :each do visit '/admin/orders/bulk_management' @@ -642,8 +651,8 @@ feature %q{ end it "all line items of the same variant" do - expect(page).to_not have_selector "tr#li_#{li1.id}", :visible => true - expect(page).to_not have_selector "tr#li_#{li2.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{li1.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{li2.id}", :visible => true expect(page).to have_selector "tr#li_#{li3.id}", :visible => true expect(page).to have_selector "tr#li_#{li4.id}", :visible => true end @@ -654,7 +663,7 @@ feature %q{ end it "shows all products and clears group buy box" do - expect(page).to_not have_selector "div#group_buy_calculation", :visible => true + expect(page).to have_no_selector "div#group_buy_calculation", :visible => true expect(page).to have_selector "tr#li_#{li1.id}", :visible => true expect(page).to have_selector "tr#li_#{li2.id}", :visible => true expect(page).to have_selector "tr#li_#{li3.id}", :visible => true @@ -668,10 +677,10 @@ feature %q{ let(:s1) { create(:supplier_enterprise, name: 'First Supplier') } let(:d1) { create(:distributor_enterprise, name: 'First Distributor') } let(:d2) { create(:distributor_enterprise, name: 'Another Distributor') } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2 ) } - let!(:line_item_distributed) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } - let!(:line_item_not_distributed) { FactoryGirl.create(:line_item, order: o2, product: create(:product, supplier: s1) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2 ) } + let!(:line_item_distributed) { create(:line_item, order: o1, product: create(:product, supplier: s1) ) } + let!(:line_item_not_distributed) { create(:line_item, order: o2, product: create(:product, supplier: s1) ) } before(:each) do @enterprise_user = create_enterprise_user @@ -692,7 +701,7 @@ feature %q{ visit '/admin/orders/bulk_management' expect(page).to have_selector "tr#li_#{line_item_distributed.id}", :visible => true - expect(page).to_not have_selector "tr#li_#{line_item_not_distributed.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{line_item_not_distributed.id}", :visible => true end end end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 9dabf93028..f7f32f6523 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -46,8 +46,9 @@ feature %q{ p2 = FactoryGirl.create(:product, available_on: Date.current-1) visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_field "available_on", with: p1.available_on.strftime("%F %T") expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T") @@ -73,7 +74,8 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 0, on_demand: true) visit '/admin/products/bulk_edit' - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') expect(page).to have_no_selector "span[name='on_hand']", text: "On demand", visible: true expect(page).to have_field "variant_on_hand", with: "4" @@ -109,7 +111,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, display_name: "something2" ) visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" + expect(page).to have_selector "a.view-variants", count: 2 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "product_name", with: v1.product.name @@ -124,6 +126,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 6) visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_selector "span[name='on_hand']", text: p1.variants.sum{ |v| v.on_hand }.to_s @@ -138,6 +141,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50) visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "price", with: "2.0", visible: false @@ -151,6 +155,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50, unit_value: 4800, unit_description: "(large bag)", display_as: "bin") visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "variant_unit_value_with_description", with: "1.2 (small bag)" @@ -217,7 +222,7 @@ feature %q{ fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." updated_variant = Spree::Variant.where(deleted_at: nil).last @@ -243,11 +248,12 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click - first("div#columns-dropdown div.menu div.menu_item", text: "Category").click - first("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click - first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown div.menu div.menu_item", text: /^Category?/).click + find("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click + find("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -267,7 +273,7 @@ feature %q{ fill_in "product_sku", with: "NEW SKU" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -293,7 +299,7 @@ feature %q{ select "Items", from: "variant_unit_with_scale" fill_in "variant_unit_name", with: "loaf" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -313,12 +319,12 @@ feature %q{ login_to_admin_section visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_field "variant_sku", with: "VARIANTSKU" expect(page).to have_field "variant_price", with: "3.0" @@ -334,7 +340,7 @@ feature %q{ expect(page).to have_selector "span[name='on_hand']", text: "10" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -352,8 +358,8 @@ feature %q{ login_to_admin_section visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') expect(page).to have_field "variant_price", with: "3.0" @@ -361,7 +367,7 @@ feature %q{ fill_in "variant_price", with: "10.0" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -378,21 +384,21 @@ feature %q{ fill_in "product_name", with: "new name 1" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 1" fill_in "product_name", with: "new name 2" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 2" fill_in "product_name", with: "original name" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "original name" @@ -404,11 +410,12 @@ feature %q{ visit '/admin/products/bulk_edit' - first("a.clone-product").click + expect(page).to have_selector "a.clone-product", count: 1 + find("a.clone-product").click fill_in "product_name", :with => "new product name" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new product name" @@ -421,7 +428,7 @@ feature %q{ visit '/admin/products/bulk_edit' - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "No changes to save." end end @@ -440,7 +447,7 @@ feature %q{ expect(page).to have_no_field "product_name", with: p2.name fill_in "product_name", :with => "new product1" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p1.reload expect(p1.name).to eq "new product1" @@ -464,7 +471,7 @@ feature %q{ expect(page).to have_selector "a.delete-product", :count => 2 within "tr#p_#{p1.id}" do - first("a.delete-product").click + find("a.delete-product").click end expect(page).to have_selector "a.delete-product", :count => 1 @@ -481,7 +488,7 @@ feature %q{ expect(page).to have_selector "a.delete-variant", :count => 3 within "tr#v_#{v3.id}" do - first("a.delete-variant").click + find("a.delete-variant").click end expect(page).to have_selector "a.delete-variant", :count => 2 @@ -509,7 +516,7 @@ feature %q{ expect(page).to have_selector "a.edit-product", :count => 2 within "tr#p_#{p1.id}" do - first("a.edit-product").click + find("a.edit-product").click end expect(URI.parse(current_url).path).to eq "/admin/products/#{p1.permalink}/edit" @@ -522,7 +529,7 @@ feature %q{ expect(page).to have_selector "a.edit-variant", :count => 2 within "tr#v_#{v1.id}" do - first("a.edit-variant").click + find("a.edit-variant").click end expect(URI.parse(current_url).path).to eq "/admin/products/#{v1.product.permalink}/variants/#{v1.id}/edit" @@ -541,7 +548,7 @@ feature %q{ expect(page).to have_selector "a.clone-product", :count => 3 within "tr#p_#{p1.id}" do - first("a.clone-product").click + find("a.clone-product").click end expect(page).to have_selector "a.clone-product", :count => 4 expect(page).to have_field "product_name", with: "COPY OF #{p1.name}" @@ -564,8 +571,9 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "PRODUCER" @@ -573,7 +581,9 @@ feature %q{ expect(page).to have_selector "th", :text => "ON HAND" expect(page).to have_selector "th", :text => "AV. ON" - first("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -696,8 +706,9 @@ feature %q{ v = p.variants.first visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -718,7 +729,7 @@ feature %q{ fill_in "variant_display_as", with: "Big Bag" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload diff --git a/spec/features/admin/cms_spec.rb b/spec/features/admin/cms_spec.rb index 2d4056b792..c200bc57e4 100644 --- a/spec/features/admin/cms_spec.rb +++ b/spec/features/admin/cms_spec.rb @@ -15,7 +15,7 @@ feature %q{ page.should have_content "ComfortableMexicanSofa" click_link 'Spree Admin' - current_path.should match(/^\/admin/) + expect(page).to have_current_path /^\/admin/ end scenario "anonymous user can't access CMS admin", js: true do @@ -26,8 +26,9 @@ feature %q{ scenario "non-admin user can't access CMS admin", js: true do login_to_consumer_section + page.should_not have_content "Login" visit cms_admin_path page.should_not have_content "ComfortableMexicanSofa" - current_path.should == root_path + expect(page).to have_current_path root_path end end diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 5d2ffedb4e..964608510d 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -9,7 +9,7 @@ feature 'Customers' do let(:managed_distributor) { create(:distributor_enterprise, owner: user) } let(:unmanaged_distributor) { create(:distributor_enterprise) } - describe "using the customers index" do + describe "using the customers index", js: true do let!(:customer1) { create(:customer, enterprise: managed_distributor) } let!(:customer2) { create(:customer, enterprise: managed_distributor) } let!(:customer3) { create(:customer, enterprise: unmanaged_distributor) } @@ -19,7 +19,7 @@ feature 'Customers' do visit admin_customers_path end - it "passes the smoke test", js: true do + it "passes the smoke test" do # Prompts for a hub for a list of my managed enterprises expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] @@ -45,7 +45,7 @@ feature 'Customers' do expect(page).to_not have_content customer1.email end - it "allows updating of attributes", js: true do + it "allows updating of attributes" do select2_select managed_distributor.name, from: "shop_id" within "tr#c_#{customer1.id}" do @@ -56,7 +56,7 @@ feature 'Customers' do find(:css, "tags-input .tags input").set "awesome\n" expect(page).to have_css ".tag_watcher.update-pending" end - click_button "Update" + click_button "Save Changes" # Every says it updated expect(page).to have_css "input#code.update-success" @@ -66,6 +66,46 @@ feature 'Customers' do expect(customer1.reload.code).to eq "new-customer-code" expect(customer1.tag_list).to eq ["awesome"] end + + describe "creating a new customer" do + context "when no shop has been selected" do + it "asks the user to select a shop" do + accept_alert 'Please select a shop first' do + click_link('New Customer') + end + end + end + + context "when a shop is selected" do + before do + select2_select managed_distributor.name, from: "shop_id" + end + + it "creates customers when the email provided is valid" do + # When an invalid email is used + expect{ + click_link('New Customer') + fill_in 'email', with: "not_an_email" + click_button 'Add Customer' + expect(page).to have_selector "#new-customer-dialog .error", text: "Please enter a valid email address" + }.to_not change{Customer.of(managed_distributor).count} + + # When an existing email is used + expect{ + fill_in 'email', with: customer1.email + click_button 'Add Customer' + expect(page).to have_selector "#new-customer-dialog .error", text: "Email is associated with an existing customer" + }.to_not change{Customer.of(managed_distributor).count} + + # When a new valid email is used + expect{ + fill_in 'email', with: "new@email.com" + click_button 'Add Customer' + expect(page).not_to have_selector "#new-customer-dialog" + }.to change{Customer.of(managed_distributor).count}.from(2).to(3) + end + end + end end end end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index aafaa5ee77..e362bf9946 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -267,6 +267,9 @@ feature %q{ scenario "updating an order cycle", js: true do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with all the settings oc = create(:order_cycle) initial_variants = oc.variants.sort_by &:id @@ -359,6 +362,7 @@ feature %q{ select 'Distributor fee 2', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' # And I click Update + expect(page).to have_selector "#save-bar" click_button 'Update and Close' # Then my order cycle should have been updated @@ -459,7 +463,7 @@ feature %q{ click_link 'Order Cycles' click_link oc.name within("table.exchanges tbody tr.supplier") { page.find('td.products input').click } - page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).click # uncheck + page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).trigger('click') # uncheck click_button "Update" # Then the master variant should have been removed from all exchanges @@ -603,13 +607,16 @@ feature %q{ visit edit_admin_order_cycle_path(oc) - # I should not see exchanges for supplier_unmanaged or distributor_unmanaged - page.all('tr.supplier').count.should == 3 - page.all('tr.distributor').count.should == 3 + # I should be able to see but not edit exchanges for supplier_unmanaged or distributor_unmanaged + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page).to have_selector "tr.supplier-#{supplier_permitted.id}" + expect(page).to have_selector "tr.supplier-#{supplier_unmanaged.id}" + expect(page.all('tr.supplier').count).to be 3 - # When I save, then those exchanges should remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." + expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" + expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" + expect(page).to have_selector "tr.distributor-#{distributor_unmanaged.id}" + expect(page.all('tr.distributor').count).to be 3 oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -618,6 +625,9 @@ feature %q{ end scenario "editing an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: distributor_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) visit edit_admin_order_cycle_path(oc) @@ -627,6 +637,7 @@ feature %q{ page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + click_button 'Update' # Then the exchanges should be removed @@ -684,8 +695,12 @@ feature %q{ # I should only see exchanges for supplier_managed AND # distributor_managed and distributor_permitted (who I have given permission to) AND # and distributor_unmanaged (who distributes my products) - page.all('tr.supplier').count.should == 1 - page.all('tr.distributor').count.should == 2 + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page.all('tr.supplier').count).to be 1 + + expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" + expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" + expect(page.all('tr.distributor').count).to be 2 # Open the products list for managed_supplier's incoming exchange within "tr.distributor-#{distributor_managed.id}" do @@ -698,10 +713,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchanges that I can't manage remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -737,8 +748,11 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # I should see exchanges for my_distributor, and the incoming exchanges supplying the variants in it - page.all('tr.supplier').count.should == 1 - page.all('tr.distributor').count.should == 1 + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page.all('tr.supplier').count).to be 1 + + expect(page).to have_selector "tr.distributor-#{my_distributor.id}" + expect(page.all('tr.distributor').count).to be 1 # Open the products list for managed_supplier's incoming exchange within "tr.supplier-#{supplier_managed.id}" do @@ -751,10 +765,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchange that I can't manage remains - click_button 'Update' - page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -868,6 +878,9 @@ feature %q{ end scenario "updating an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with pickup time and instructions fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise) @@ -902,6 +915,8 @@ feature %q{ # When I update, or update and close, both work click_button 'Update' page.should have_content 'Your order cycle has been updated.' + + fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'yyz' click_button 'Update and Close' # Then my order cycle should have been updated @@ -921,7 +936,7 @@ feature %q{ # And my pickup time and instructions should have been saved ex = oc.exchanges.outgoing.first ex.pickup_time.should == 'xy' - ex.pickup_instructions.should == 'zzy' + ex.pickup_instructions.should == 'yyz' end end diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 725495b74b..18b3466106 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -21,7 +21,7 @@ feature %q{ create :check_payment, order: @order, amount: @order.total end - scenario "creating an order with distributor and order cycle", retry: 3 do + scenario "creating an order with distributor and order cycle" do distributor_disabled = create(:distributor_enterprise) create(:simple_order_cycle, name: 'Two') @@ -31,12 +31,16 @@ feature %q{ click_link 'New Order' # Distributors without an order cycle should be shown as disabled - page.should have_selector "option[value='#{distributor_disabled.id}'][disabled='disabled']" + open_select2('#s2id_order_distributor_id') + page.should have_selector "ul.select2-results li.select2-result.select2-disabled", text: distributor_disabled.name + close_select2('#s2id_order_distributor_id') + + # Order cycle selector should be disabled + page.should have_selector "#s2id_order_order_cycle_id.select2-container-disabled" # When we select a distributor, it should limit order cycle selection to those for that distributor - page.should_not have_select2 'order_order_cycle_id' - select @distributor.name, from: 'order_distributor_id' - page.should have_select2 'order_order_cycle_id', options: ['', 'One (open)'] + select2_select @distributor.name, from: 'order_distributor_id' + page.should have_select2 'order_order_cycle_id', options: ['One (open)'] select2_select @order_cycle.name, from: 'order_order_cycle_id' page.should have_content 'ADD PRODUCT' @@ -80,7 +84,7 @@ feature %q{ click_edit - select d.name, from: 'order_distributor_id' + select2_select d.name, from: 'order_distributor_id' select2_select oc.name, from: 'order_order_cycle_id' click_button 'Update And Recalculate Fees' @@ -106,7 +110,7 @@ feature %q{ visit '/admin/orders' page.find('td.actions a.icon-edit').click - page.should have_no_select 'order_distributor_id' + page.should_not have_select2 'order_distributor_id' page.should_not have_select2 'order_order_cycle_id' page.should have_selector 'p', text: "Distributor: #{@order.distributor.name}" @@ -124,7 +128,7 @@ feature %q{ login_to_admin_section visit '/admin/orders' click_link 'New Order' - select @distributor.name, from: 'order_distributor_id' + select2_select @distributor.name, from: 'order_distributor_id' select2_select @order_cycle.name, from: 'order_order_cycle_id' targetted_select2_search @product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' click_link 'Add' @@ -149,7 +153,7 @@ feature %q{ login_to_admin_section visit '/admin/orders' - current_path.should == spree.admin_orders_path + expect(page).to have_current_path spree.admin_orders_path # click the 'capture' link for the order page.find("[data-action=capture][href*=#{@order.number}]").click @@ -161,7 +165,7 @@ feature %q{ @order.payment_state.should == "paid" # we should still be on the same page - current_path.should == spree.admin_orders_path + expect(page).to have_current_path spree.admin_orders_path end @@ -208,7 +212,7 @@ feature %q{ visit '/admin/orders' click_link 'New Order' - select distributor1.name, from: 'order_distributor_id' + select2_select distributor1.name, from: 'order_distributor_id' select2_select order_cycle1.name, from: 'order_order_cycle_id' expect(page).to have_content 'ADD PRODUCT' @@ -218,8 +222,8 @@ feature %q{ page.has_selector? "table.index tbody[data-hook='admin_order_form_line_items'] tr" # Wait for JS expect(page).to have_selector 'td', text: product.name - expect(page).to have_select 'order_distributor_id', with_options: [distributor1.name] - expect(page).to_not have_select 'order_distributor_id', with_options: [distributor2.name] + expect(page).to have_select2 'order_distributor_id', with_options: [distributor1.name] + expect(page).to_not have_select2 'order_distributor_id', with_options: [distributor2.name] expect(page).to have_select2 'order_order_cycle_id', with_options: ["#{order_cycle1.name} (open)"] expect(page).to_not have_select2 'order_order_cycle_id', with_options: ["#{order_cycle2.name} (open)"] diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb index abb5feaf3e..b736155372 100644 --- a/spec/features/admin/payment_method_spec.rb +++ b/spec/features/admin/payment_method_spec.rb @@ -94,7 +94,7 @@ feature %q{ click_link "Payment Methods" end click_link 'Create One Now' - current_path.should == spree.new_admin_payment_method_path + expect(page).to have_current_path spree.new_admin_payment_method_path end it "creates payment methods" do diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index a3a08c2466..0af93d19c3 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -255,12 +255,12 @@ feature %q{ it "deletes overrides when values are cleared" do first("div#columns-dropdown", :text => "COLUMNS").click first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click - first("div#columns-dropdown div.menu div.menu_item", text: "Reset Stock Level").click + first("div#columns-dropdown div.menu div.menu_item", text: "Enable Stock Reset?").click first("div#columns-dropdown", :text => "COLUMNS").click # Clearing values by 'inheriting' first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Inheritance").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inherit?").click first("div#columns-dropdown", :text => "COLUMNS").click check "variant-overrides-#{variant3.id}-inherit" diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index b01b04fe84..63f08efb71 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -18,7 +18,7 @@ feature "Authentication", js: true, retry: 3 do fill_in "Password", with: user.password click_login_button page.should have_content "Find local producers" - current_path.should == producers_path + expect(page).to have_current_path producers_path end end diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb index 0e54011f1c..9b2602c1c6 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -89,6 +89,7 @@ feature "Registration", js: true do # Done expect(page).to have_content "Finished!" + expect(page).to have_content "We've sent a confirmation email to #{user.email} if it hasn't been activated before." e.reload expect(e.website).to eq "www.shop.com" expect(e.facebook).to eq "FaCeBoOk" diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index dceecd43f8..9b03259ec5 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -29,7 +29,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path page.should have_text distributor.name find("#tab_about a").click - first("distributor img")['src'].should == distributor.logo.url(:thumb) + first("distributor img")['src'].should include distributor.logo.url(:thumb) end it "shows the producers for a distributor" do diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 4edaa702e0..9625640fe7 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -43,12 +43,12 @@ feature 'Shops', js: true do it "should link to the hub page" do follow_active_table_node distributor.name - current_path.should == enterprise_shop_path(distributor) + expect(page).to have_current_path enterprise_shop_path(distributor) end it "should show hub producer modals" do expand_active_table_node distributor.name - page.should have_content producer.name + expect(page).to have_content producer.name open_enterprise_modal producer modal_should_be_open_for producer end diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 07202027b4..37c56918d4 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -8,9 +8,10 @@ //= require lodash.underscore.js //= require angular-flash.min.js //= require shared/ng-tags-input.min.js -//= require shared/mm-foundation-tpls-0.2.2.min.js -//= require textAngular.min.js +//= require shared/mm-foundation-tpls-0.8.0.min.js +//= require textAngular-rangy.min.js //= require textAngular-sanitize.min.js +//= require textAngular.min.js //= require moment angular.module('templates', []) diff --git a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee index de3a3d5d55..450041a417 100644 --- a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee @@ -20,6 +20,7 @@ describe "VariantOverridesCtrl", -> $provide.value 'variantOverrides', variantOverrides $provide.value 'dirtyVariantOverrides', dirtyVariantOverrides $provide.value 'inventoryItems', inventoryItems + $provide.value 'columns', [] null inject ($controller, _VariantOverrides_, _DirtyVariantOverrides_, _StatusMessage_) -> diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 6981ef4e4f..7854700dfd 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -4,16 +4,21 @@ describe "CustomersCtrl", -> beforeEach -> module('admin.customers') + module ($provide) -> + $provide.value 'columns', [] + null + inject ($controller, $rootScope, _CustomerResource_, $httpBackend) -> scope = $rootScope http = $httpBackend $controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: {}} - this.addMatchers - toAngularEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } it "has no shop pre-selected", -> - expect(scope.shop).toEqual {} + expect(scope.CurrentShop.shop).toEqual {} describe "setting the shop on scope", -> customer = { id: 5, email: 'someone@email.com'} @@ -22,21 +27,21 @@ describe "CustomersCtrl", -> beforeEach -> http.expectGET('/admin/customers.json?enterprise_id=1').respond 200, customers scope.$apply -> - scope.shop = {id: 1} + scope.CurrentShop.shop = {id: 1} http.flush() it "retrievs the list of customers", -> - expect(scope.customers).toAngularEqual customers + expect(scope.customers).toDeepEqual customers describe "scope.add", -> it "creates a new customer", -> email = "customer@example.org" newCustomer = {id: 6, email: email} - customers.push(newCustomer) + customers.unshift(newCustomer) http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=1').respond 200, newCustomer scope.add(email) http.flush() - expect(scope.customers).toAngularEqual customers + expect(scope.customers).toDeepEqual customers describe "scope.deleteCustomer", -> it "deletes a customer", -> @@ -46,7 +51,7 @@ describe "CustomersCtrl", -> scope.deleteCustomer(customer) http.flush() expect(scope.customers.length).toBe 1 - expect(scope.customers[0]).not.toAngularEqual customer + expect(scope.customers[0]).not.toDeepEqual customer describe "scope.findTags", -> tags = [ @@ -55,7 +60,7 @@ describe "CustomersCtrl", -> { text: 'three' } ] beforeEach -> - http.expectGET('/admin/tags.json?enterprise_id=1').respond 200, tags + http.expectGET('/admin/tag_rules/map_by_tag.json?enterprise_id=1').respond 200, tags it "retrieves the tag list", -> promise = scope.findTags('') @@ -63,7 +68,7 @@ describe "CustomersCtrl", -> promise.then (data) -> result = data http.flush() - expect(result).toAngularEqual tags + expect(result).toDeepEqual tags it "filters the tag list", -> filtered_tags = [ @@ -75,4 +80,4 @@ describe "CustomersCtrl", -> promise.then (data) -> result = data http.flush() - expect(result).toAngularEqual filtered_tags + expect(result).toDeepEqual filtered_tags diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee index b504ff8f90..450876c9e6 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee @@ -53,7 +53,7 @@ describe "enterpriseCtrl", -> expect(enterprise.users).not.toContain u4 it "ignores objects that are already in the list, and alerts the user", -> - spyOn(window, "alert").andCallThrough() + spyOn(window, "alert").and.callThrough() u4 = { id: 3, email: "email-doesn't-matter.com" } scope.addManager u4 expect(enterprise.users).not.toContain u4 diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee index 6e8bdd8040..24c4035a3e 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee @@ -5,10 +5,14 @@ describe "EnterprisesCtrl", -> beforeEach -> module('admin.enterprises') + module ($provide) -> + $provide.value 'columns', [] + null + inject ($controller, $rootScope, _Enterprises_) -> scope = $rootScope Enterprises = _Enterprises_ - spyOn(Enterprises, "index").andReturn "list of enterprises" + spyOn(Enterprises, "index").and.returnValue "list of enterprises" ctrl = $controller 'enterprisesCtrl', {$scope: scope, Enterprises: Enterprises} describe "setting the shop on scope", -> diff --git a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee index b74595b775..8fca1c6ab5 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee @@ -20,10 +20,10 @@ describe "indexPanelCtrl", -> deferred = null beforeEach inject ($q) -> - spyOn(scope, "saved").andReturn false + spyOn(scope, "saved").and.returnValue false spyOn(scope, "$emit") deferred = $q.defer() - spyOn(Enterprises, "save").andReturn(deferred.promise) + spyOn(Enterprises, "save").and.returnValue(deferred.promise) scope.save() it "sets scope.saving to true", -> diff --git a/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee index 888a42daa3..bcecc1c5a6 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee @@ -28,21 +28,21 @@ describe "permalinkCtrl", -> it "sends a request to PermalinkChecker when permalink is changed", -> deferred.resolve("") promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect(PermalinkChecker.check).toHaveBeenCalled() it "sets available to '' when PermalinkChecker resolves permalink to the existing permalink on Enterprise ", -> deferred.resolve({permalink: "something"}) promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect($scope.availability).toEqual "" it "sets available and permalink when PermalinkChecker resolves", -> deferred.resolve({ available: "Available", permalink: "permalink"}) promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect(Enterprise.permalink).toEqual "permalink" expect($scope.availability).toEqual "Available" @@ -51,7 +51,7 @@ describe "permalinkCtrl", -> $scope.availability = "Some Availability" deferred.reject() promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect($scope.availability).toEqual "Some Availability" expect(Enterprise.permalink).toEqual "somethingelse" diff --git a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee index e88aeb44f2..c4a724a137 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee @@ -17,8 +17,8 @@ describe "menuCtrl", -> inject ($rootScope, $controller, _SideMenu_) -> scope = $rootScope SideMenu = _SideMenu_ - spyOn(SideMenu, "select").andCallThrough() - spyOn(SideMenu, "setItems").andCallThrough() + spyOn(SideMenu, "select").and.callThrough() + spyOn(SideMenu, "setItems").and.callThrough() ctrl = $controller 'sideMenuCtrl', {$scope: scope, enterprise: enterprise, SideMenu: SideMenu, enterprisePermissions: {}} describe "initialisation", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee index 0a719b203a..c4e83cd283 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee @@ -27,16 +27,16 @@ describe "EnterprisePaymentMethods service", -> describe "determining payment method colour", -> it "returns 'blue' when at least one payment method is selected", -> - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 1 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 1 expect(EnterprisePaymentMethods.displayColor()).toBe "blue" it "returns 'red' when no payment methods are selected", -> - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 0 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 0 expect(EnterprisePaymentMethods.displayColor()).toBe "red" it "returns 'red' when no payment methods exist", -> EnterprisePaymentMethods.paymentMethods = [] - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 1 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 1 expect(EnterprisePaymentMethods.displayColor()).toBe "red" describe "counting selected payment methods", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee index 4b857023b8..d8b83cf6c7 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee @@ -27,16 +27,16 @@ describe "EnterpriseShippingMethods service", -> describe "determining shipping method colour", -> it "returns 'blue' when at least one shipping method is selected", -> - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 1 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 1 expect(EnterpriseShippingMethods.displayColor()).toBe "blue" it "returns 'red' when no shipping methods are selected", -> - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 0 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 0 expect(EnterpriseShippingMethods.displayColor()).toBe "red" it "returns 'red' when no shipping methods exist", -> EnterpriseShippingMethods.shippingMethods = [] - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 1 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 1 expect(EnterpriseShippingMethods.displayColor()).toBe "red" describe "counting selected shipping methods", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index fb653d2df4..702ef2cc1c 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -4,9 +4,10 @@ describe "Enterprises service", -> beforeEach -> module 'admin.enterprises' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _Enterprises_, _EnterpriseResource_) -> Enterprises = _Enterprises_ @@ -98,14 +99,14 @@ describe "Enterprises service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(Enterprises, "diff").andReturn ["attr1", "attr2"] + spyOn(Enterprises, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(Enterprises.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(Enterprises, "diff").andReturn [] + spyOn(Enterprises, "diff").and.returnValue [] it "returns false", -> expect(Enterprises.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee index 2161afe7de..92eba790c3 100644 --- a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee @@ -5,6 +5,9 @@ describe "PanelRow directive", -> beforeEach -> module 'admin.indexUtils' + module ($provide) -> + $provide.value 'columns', [] + null beforeEach inject ($rootScope, $compile, $injector, $templateCache, _Panels_) -> Panels = _Panels_ diff --git a/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee index 2bff5e5a73..66122578a7 100644 --- a/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee @@ -3,19 +3,21 @@ describe "Columns service", -> beforeEach -> module 'admin.indexUtils' - + module ($provide) -> + $provide.value 'columns', [ + { column_name: 'col1', visible: true } + { column_name: 'col2', visible: false } + ] + null inject (_Columns_) -> Columns = _Columns_ - describe "setting columns", -> + describe "initialising columns", -> it "sets resets @columns and copies each column of the provided object across", -> - Columns.setColumns({ name: { visible: true } }) - expect(Columns.columns).toEqual { name: { visible: true } } + expect(Columns.columns).toEqual { col1: { column_name: 'col1', visible: true }, col2: { column_name: 'col2', visible: false } } - it "calls calculateVisibleCount", -> - spyOn(Columns, "calculateVisibleCount") - Columns.setColumns({ name: { visible: true } }) - expect(Columns.calculateVisibleCount).toHaveBeenCalled() + it "updates visibleCount", -> + expect(Columns.visibleCount).toBe 1 describe "toggling a column", -> it "switches the visibility of the given column", -> diff --git a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee index a55d9ffa61..7ee69cf88e 100644 --- a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee @@ -29,7 +29,7 @@ describe "Panels service", -> describe "when no panel is currently selected", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn(null) + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue(null) Panels.toggle(12, 'panel_name') it "calls #open on the scope", -> @@ -37,7 +37,7 @@ describe "Panels service", -> describe "when #toggle is called for the currently selected panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn('panel_name') + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('panel_name') Panels.toggle(12, 'panel_name') it "calls #close on the scope", -> @@ -45,7 +45,7 @@ describe "Panels service", -> describe "when #toggle is called for a different panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn('some_other_panel_name') + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('some_other_panel_name') Panels.toggle(12, 'panel_name') it "calls #setSelected on the scope", -> diff --git a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee index 31b85df217..df0eaea495 100644 --- a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee @@ -4,7 +4,7 @@ describe "Pending Changes", -> beforeEach -> resourcesMock = - update: jasmine.createSpy('update').andCallFake (change) -> + update: jasmine.createSpy('update').and.callFake (change) -> $promise: then: (successFn, errorFn) -> return successFn({propertyName: "new_value"}) if change.success @@ -88,7 +88,7 @@ describe "Pending Changes", -> it "sends the correct object to dataSubmitter", -> pendingChanges.submit change - expect(resourcesMock.update.calls.length).toEqual 1 + expect(resourcesMock.update.calls.count()).toBe 1 expect(resourcesMock.update).toHaveBeenCalledWith change describe "successful request", -> @@ -96,9 +96,9 @@ describe "Pending Changes", -> change.success = true it "calls remove with id and attribute name", -> - spyOn(pendingChanges, "remove").andCallFake(->) + spyOn(pendingChanges, "remove").and.callFake(->) pendingChanges.submit change - expect(pendingChanges.remove.calls.length).toEqual 1 + expect(pendingChanges.remove.calls.count()).toBe 1 expect(pendingChanges.remove).toHaveBeenCalledWith 1, "propertyName" it "calls reset on the relevant scope", -> @@ -114,7 +114,7 @@ describe "Pending Changes", -> change.success = false it "does not call remove", -> - spyOn(pendingChanges, "remove").andCallFake(->) + spyOn(pendingChanges, "remove").and.callFake(->) pendingChanges.submit change expect(pendingChanges.remove).not.toHaveBeenCalled() @@ -128,13 +128,13 @@ describe "Pending Changes", -> describe "cycling through all changes to submit to server", -> it "sends the correct object to dataSubmitter", -> - spyOn(pendingChanges, "submit").andCallFake(->) + spyOn(pendingChanges, "submit").and.callFake(->) pendingChanges.pendingChanges = 1: { "prop1": { attr: "prop1", value: 1 }, "prop2": { attr: "prop2", value: 2 } } 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop2", value: 4 } } 7: { "prop2": { attr: "prop2", value: 5 } } pendingChanges.submitAll() - expect(pendingChanges.submit.calls.length).toEqual 5 + expect(pendingChanges.submit.calls.count()).toBe 5 expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 1 } expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 2 } expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 2 } @@ -142,7 +142,7 @@ describe "Pending Changes", -> expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 5 } it "returns an array of promises representing all sumbit requests", -> - spyOn(pendingChanges, "submit").andCallFake (change) -> change.value + spyOn(pendingChanges, "submit").and.callFake (change) -> change.value pendingChanges.pendingChanges = 1: { "prop1": { attr: "prop1", value: 1 } } 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop1", value: 4 } } diff --git a/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee index e7dedb4e92..117231ca14 100644 --- a/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee @@ -8,7 +8,7 @@ describe "switchClass service", -> elementMock = addClass: addClass removeClass: removeClass - timeoutMock = jasmine.createSpy('timeout').andReturn "new timeout" + timeoutMock = jasmine.createSpy('timeout').and.returnValue "new timeout" timeoutMock.cancel = jasmine.createSpy('timeout.cancel') beforeEach -> @@ -22,14 +22,14 @@ describe "switchClass service", -> it "calls addClass on the element once", -> switchClassService elementMock, "addClass", [], false expect(addClass).toHaveBeenCalledWith "addClass" - expect(addClass.calls.length).toEqual 1 + expect(addClass.calls.count()).toBe 1 it "calls removeClass on the element for ", -> switchClassService elementMock, "", ["remClass1", "remClass2", "remClass3"], false expect(removeClass).toHaveBeenCalledWith "remClass1" expect(removeClass).toHaveBeenCalledWith "remClass2" expect(removeClass).toHaveBeenCalledWith "remClass3" - expect(removeClass.calls.length).toEqual 3 + expect(removeClass.calls.count()).toBe 3 it "call cancel on element.timout only if it exists", -> switchClassService elementMock, "", [], false diff --git a/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee index 7333882d5d..77df49d621 100644 --- a/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee @@ -9,7 +9,7 @@ describe "Views service", -> describe "setting views", -> beforeEach -> - spyOn(Views, "selectView").andCallThrough() + spyOn(Views, "selectView").and.callThrough() Views.setViews view1: { name: 'View1', visible: true } view2: { name: 'View2', visible: false } diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index 7a0b165e2b..a1c291f5b0 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -4,10 +4,14 @@ describe "LineItemsCtrl", -> beforeEach -> module "admin.lineItems" + module ($provide) -> + $provide.value 'columns', [] + null - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } beforeEach inject(($controller, $rootScope, $httpBackend, _$timeout_, _VariantUnitManager_, _Enterprises_, _Orders_, _LineItems_, _OrderCycles_) -> scope = $rootScope.$new() @@ -19,9 +23,9 @@ describe "LineItemsCtrl", -> LineItems = _LineItems_ OrderCycles = _OrderCycles_ VariantUnitManager = _VariantUnitManager_ - spyOn(window, "daysFromToday").andReturn "SomeDate" - spyOn(window, "formatDate").andReturn "SomeDate" - spyOn(window, "parseDate").andReturn "SomeDate" + spyOn(window, "daysFromToday").and.returnValue "SomeDate" + spyOn(window, "formatDate").and.returnValue "SomeDate" + spyOn(window, "parseDate").and.returnValue "SomeDate" supplier = { id: 1, name: "Supplier" } distributor = { id: 5, name: "Distributor" } @@ -51,7 +55,7 @@ describe "LineItemsCtrl", -> expect(scope.quickSearch).toBeUndefined() it "will not have reset the form state to pristine", -> - expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 0 + expect(scope.bulk_order_form.$setPristine.calls.count()).toBe 0 describe "after data is returned", -> beforeEach -> @@ -87,13 +91,13 @@ describe "LineItemsCtrl", -> expect(scope.quickSearch).toBe = "" it "resets the form state to pristine", -> - expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 1 + expect(scope.bulk_order_form.$setPristine.calls.count()).toBe 1 describe "deleting a line item", -> order = line_item1 = line_item2 = null beforeEach inject((LineItemResource) -> - spyOn(window,"confirm").andReturn true + spyOn(window,"confirm").and.returnValue true order = { number: "R12345678" } line_item1 = new LineItemResource({ id: 1, order: order }) line_item2 = new LineItemResource({ id: 2, order: order }) @@ -247,7 +251,7 @@ describe "LineItemsCtrl", -> # A Units Variant is an API object which holds unit properies of a variant beforeEach -> - spyOn(Math,"round").andCallThrough() + spyOn(Math,"round").and.callThrough() it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> expect(scope.formattedValueWithUnitName(1,{})).toEqual '' @@ -267,14 +271,14 @@ describe "LineItemsCtrl", -> it "calls Math.round with the quotient of scale and value, multiplied by 1000", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 5 + spyOn(VariantUnitManager, "getScale").and.returnValue 5 scope.formattedValueWithUnitName(10, unitsVariant) expect(Math.round).toHaveBeenCalledWith 10/5 * 1000 it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 1000 - spyOn(VariantUnitManager, "getUnitName").andReturn "kg" + spyOn(VariantUnitManager, "getScale").and.returnValue 1000 + spyOn(VariantUnitManager, "getUnitName").and.returnValue "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" describe "updating the price upon updating the weight of a line item", -> diff --git a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee index 44948ecb37..ef428377c0 100644 --- a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee @@ -4,9 +4,10 @@ describe "LineItems service", -> beforeEach -> module 'admin.lineItems' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _LineItems_, _LineItemResource_) -> LineItems = _LineItems_ @@ -72,14 +73,14 @@ describe "LineItems service", -> describe "#isSaved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(LineItems, "diff").andReturn ["attr1", "attr2"] + spyOn(LineItems, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(LineItems.isSaved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(LineItems, "diff").andReturn [] + spyOn(LineItems, "diff").and.returnValue [] it "returns false", -> expect(LineItems.isSaved({})).toBe true diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee index 09d5524fac..7404bbead2 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee @@ -18,11 +18,11 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> addSupplier: jasmine.createSpy() addDistributor: jasmine.createSpy() setExchangeVariants: jasmine.createSpy() - new: jasmine.createSpy().andReturn order_cycle + new: jasmine.createSpy().and.returnValue order_cycle Enterprise = - get: jasmine.createSpy().andReturn {id: 123} + get: jasmine.createSpy().and.returnValue {id: 123} index: jasmine.createSpy() - suppliedVariants: jasmine.createSpy().andReturn('supplied variants') + suppliedVariants: jasmine.createSpy().and.returnValue('supplied variants') EnterpriseFee = index: jasmine.createSpy() ocInstance = {} diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee index dbad5a9f05..f5ccd25aac 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee @@ -10,7 +10,8 @@ describe "AdminSimpleEditOrderCycleCtrl", -> outgoing_exchange = {} beforeEach -> - scope = {} + scope = + $watch: jasmine.createSpy('$watch') location = absUrl: -> 'example.com/admin/order_cycles/27/edit' diff --git a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee index 2ddfe92407..aebdb19f6f 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee @@ -4,9 +4,10 @@ describe "OrderCycles service", -> beforeEach -> module 'admin.orderCycles' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _OrderCycles_, _OrderCycleResource_) -> OrderCycles = _OrderCycles_ @@ -98,14 +99,14 @@ describe "OrderCycles service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(OrderCycles, "diff").andReturn ["attr1", "attr2"] + spyOn(OrderCycles, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(OrderCycles.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(OrderCycles, "diff").andReturn [] + spyOn(OrderCycles, "diff").and.returnValue [] it "returns false", -> expect(OrderCycles.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee index cefb1d7a10..c3d6646144 100644 --- a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee @@ -4,9 +4,10 @@ describe "Orders service", -> beforeEach -> module 'admin.orders' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _Orders_, _OrderResource_) -> Orders = _Orders_ @@ -74,14 +75,14 @@ describe "Orders service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(Orders, "diff").andReturn ["attr1", "attr2"] + spyOn(Orders, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(Orders.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(Orders, "diff").andReturn [] + spyOn(Orders, "diff").and.returnValue [] it "returns false", -> expect(Orders.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee b/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee index 198a09a8b8..19353bf5dd 100644 --- a/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee @@ -15,26 +15,26 @@ describe "Option Value Namer", -> it "when description is blank", -> v.unit_description = null - spyOn(namer, "value_scaled").andReturn true - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue true + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] expect(namer.name()).toBe "valueunit" it "when description is present", -> v.unit_description = 'desc' - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] - spyOn(namer, "value_scaled").andReturn true + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue true expect(namer.name()).toBe "valueunit desc" it "when value is blank and description is present", -> v.unit_description = 'desc' - spyOn(namer, "option_value_value_unit").andReturn [null, null] - spyOn(namer, "value_scaled").andReturn true + spyOn(namer, "option_value_value_unit").and.returnValue [null, null] + spyOn(namer, "value_scaled").and.returnValue true expect(namer.name()).toBe "desc" it "spaces value and unit when value is unscaled", -> v.unit_description = null - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] - spyOn(namer, "value_scaled").andReturn false + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue false expect(namer.name()).toBe "value unit" describe "determining if a variant's value is scaled", -> @@ -44,7 +44,7 @@ describe "Option Value Namer", -> p = {} v = { product: p } namer = new OptionValueNamer(v) - + it "returns true when the product has a scale", -> p.variant_unit_scale = 1000 expect(namer.value_scaled()).toBe true @@ -120,4 +120,4 @@ describe "Option Value Namer", -> p.variant_unit_scale = null p.variant_unit_name = 'foo' v.unit_value = null - expect(namer.option_value_value_unit()).toEqual [null, null] \ No newline at end of file + expect(namer.option_value_value_unit()).toEqual [null, null] diff --git a/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee b/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee index 1c6044b66e..0840d69c28 100644 --- a/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee +++ b/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee @@ -73,21 +73,21 @@ describe "SideMenu service", -> describe "hiding an item by name", -> it "sets visible to false on the response from find_by_name", -> mockItem = { visible: true } - spyOn(SideMenu, 'find_by_name').andReturn mockItem + spyOn(SideMenu, 'find_by_name').and.returnValue mockItem SideMenu.hide_item_by_name() expect(mockItem.visible).toBe false it "doesn't crash if null is returned from find_by_name", -> - spyOn(SideMenu, 'find_by_name').andReturn null + spyOn(SideMenu, 'find_by_name').and.returnValue null SideMenu.hide_item_by_name() describe "showing an item by name", -> it "sets visible to false on the response from find_by_name", -> mockItem = { visible: false } - spyOn(SideMenu, 'find_by_name').andReturn mockItem + spyOn(SideMenu, 'find_by_name').and.returnValue mockItem SideMenu.show_item_by_name() expect(mockItem.visible).toBe true it "doesn't crash if null is returned from find_by_name", -> - spyOn(SideMenu, 'find_by_name').andReturn null + spyOn(SideMenu, 'find_by_name').and.returnValue null SideMenu.show_item_by_name() diff --git a/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee index 1e132ec07d..4a0c304ee8 100644 --- a/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee @@ -47,7 +47,7 @@ describe "TagRulesCtrl", -> beforeEach inject ($httpBackend) -> rule = scope.tagGroups[0].rules[0] - spyOn(window, "confirm").andReturn(true) + spyOn(window, "confirm").and.returnValue(true) $httpBackend.expectDELETE('/admin/enterprises/45/tag_rules/1.json').respond(status: 204) scope.deleteTagRule(scope.tagGroups[0], rule) $httpBackend.flush() diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee deleted file mode 100644 index 3e7b27ae73..0000000000 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ /dev/null @@ -1,427 +0,0 @@ -describe "AdminOrderMgmtCtrl", -> - ctrl = scope = httpBackend = VariantUnitManager = null - - beforeEach -> - module "ofn.admin", ($provide) -> - $provide.value 'SpreeApiKey', 'API_KEY' - return - beforeEach inject(($controller, $rootScope, $httpBackend, _VariantUnitManager_) -> - scope = $rootScope.$new() - ctrl = $controller - httpBackend = $httpBackend - VariantUnitManager = _VariantUnitManager_ - spyOn(window, "formatDate").andReturn "SomeDate" - - ctrl "AdminOrderMgmtCtrl", {$scope: scope} - ) - - describe "loading data upon initialisation", -> - it "gets a list of suppliers, a list of distributors and a list of Order Cycles and then calls fetchOrders", -> - returnedSuppliers = ["list of suppliers"] - returnedDistributors = ["list of distributors"] - returnedOrderCycles = [ "oc1", "oc2", "oc3" ] - httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" - httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers - httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").respond returnedDistributors - httpBackend.expectGET("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=SomeDate").respond returnedOrderCycles - spyOn(scope, "initialiseVariables").andCallThrough() - spyOn(scope, "fetchOrders").andReturn "nothing" - #spyOn(returnedSuppliers, "unshift") - #spyOn(returnedDistributors, "unshift") - #spyOn(returnedOrderCycles, "unshift") - scope.initialise() - httpBackend.flush() - - expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] - - expect(scope.initialiseVariables.calls.length).toBe 1 - expect(scope.fetchOrders.calls.length).toBe 1 - expect(scope.spree_api_key_ok).toBe true - - describe "fetching orders", -> - beforeEach -> - scope.initialiseVariables() - httpBackend.expectGET("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" - - it "makes a call to dataFetcher, with current start and end date parameters", -> - scope.fetchOrders() - - it "calls resetOrders after data has been received", -> - spyOn scope, "resetOrders" - scope.fetchOrders() - httpBackend.flush() - expect(scope.resetOrders).toHaveBeenCalledWith "list of orders" - - it "sets the loading property to true before fetching orders and unsets it when loading is complete", -> - spyOn scope, "resetOrders" - scope.fetchOrders() - expect(scope.loading).toEqual true - httpBackend.flush() - expect(scope.loading).toEqual false - - describe "resetting orders", -> - beforeEach -> - spyOn(scope, "matchObject").andReturn "nothing" - spyOn(scope, "resetLineItems").andReturn "nothing" - scope.resetOrders [ "order1", "order2", "order3" ] - - it "sets the value of $scope.orders to the data received", -> - expect(scope.orders).toEqual [ "order1", "order2", "order3" ] - - it "makes a call to $scope.resetLineItems", -> - expect(scope.resetLineItems).toHaveBeenCalled() - - describe "resetting line items", -> - order1 = order2 = order3 = null - - beforeEach -> - spyOn(scope, "matchObject").andReturn "nothing" - spyOn(scope, "lineItemOrder").andReturn "copied order" - order1 = { name: "order1", line_items: [ { name: "line_item1.1" }, { name: "line_item1.1" }, { name: "line_item1.1" } ] } - order2 = { name: "order2", line_items: [ { name: "line_item2.1" }, { name: "line_item2.1" }, { name: "line_item2.1" } ] } - order3 = { name: "order3", line_items: [ { name: "line_item3.1" }, { name: "line_item3.1" }, { name: "line_item3.1" } ] } - scope.orders = [ order1, order2, order3 ] - scope.resetLineItems() - - it "creates $scope.lineItems by flattening the line_items arrays in each order object", -> - expect(scope.lineItems.length).toEqual 9 - expect(scope.lineItems[0].name).toEqual "line_item1.1" - expect(scope.lineItems[3].name).toEqual "line_item2.1" - expect(scope.lineItems[6].name).toEqual "line_item3.1" - - it "adds a reference to a modified parent order object to each line item", -> - expect(scope.lineItemOrder.calls.length).toEqual scope.orders.length - expect("copied order").toEqual line_item.order for line_item in scope.lineItems - - it "calls matchObject once for each line item", -> - expect(scope.matchObject.calls.length).toEqual scope.lineItems.length - - describe "copying orders", -> - order1copy = null - - beforeEach -> - spyOn(scope, "lineItemOrder").andCallThrough() - spyOn(scope, "matchObject").andReturn "matched object" - order1 = { name: "order1", line_items: [ ] } - scope.orders = [ order1 ] - order1copy = scope.lineItemOrder order1 - - it "calls removes the line_items attribute of the order, in order to avoid circular referencing)", -> - expect(order1copy.hasOwnProperty("line_items")).toEqual false - - it "calls matchObject twice for each order (once for distributor and once for order cycle)", -> - expect(scope.matchObject.calls.length).toEqual scope.lineItemOrder.calls.length * 2 - expect(order1copy.distributor).toEqual "matched object" - expect(order1copy.distributor).toEqual "matched object" - - describe "matching objects", -> - it "returns the first matching object in the list", -> - list_item1 = - id: 1 - name: "LI1" - - list_item2 = - id: 2 - name: "LI2" - - test_item = - id: 2 - name: "LI2" - - expect(list_item2 is test_item).not.toEqual true - list = [ - list_item1 - list_item2 - ] - - returned_item = scope.matchObject list, test_item, null - expect(returned_item is list_item2).toEqual true - - it "returns the default provided if no matching item is found", -> - list_item1 = - id: 1 - name: "LI1" - - list_item2 = - id: 2 - name: "LI2" - - test_item = - id: 1 - name: "LI2" - - expect(list_item2 is test_item).not.toEqual true - list = [ - list_item1 - list_item2 - ] - - returned_item = scope.matchObject list, test_item, null - expect(returned_item is null).toEqual true - - describe "deleting a line item", -> - order = line_item1 = line_item2 = null - - beforeEach -> - scope.initialiseVariables() - spyOn(window,"confirm").andReturn true - order = { number: "R12345678", line_items: [] } - line_item1 = { id: 1, order: order } - line_item2 = { id: 2, order: order } - order.line_items = [ line_item1, line_item2 ] - - it "sends a delete request via the API", -> - httpBackend.expectDELETE("/api/orders/#{line_item1.order.number}/line_items/#{line_item1.id}").respond "nothing" - scope.deleteLineItem line_item1 - httpBackend.flush() - - it "does not remove line_item from the line_items array when request is not successful", -> - httpBackend.expectDELETE("/api/orders/#{line_item1.order.number}/line_items/#{line_item1.id}").respond 404, "NO CONTENT" - scope.deleteLineItem line_item1 - httpBackend.flush() - expect(order.line_items).toEqual [line_item1, line_item2] - - describe "deleting 'checked' line items", -> - line_item1 = line_item2 = line_item3 = line_item4 = null - - beforeEach -> - line_item1 = { name: "line item 1", checked: false } - line_item2 = { name: "line item 2", checked: true } - line_item3 = { name: "line item 3", checked: false } - line_item4 = { name: "line item 4", checked: true } - scope.lineItems = [ line_item1, line_item2, line_item3, line_item4 ] - - it "calls deletedLineItem for each 'checked' line item", -> - spyOn(scope, "deleteLineItem") - scope.deleteLineItems(scope.lineItems) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item2) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item4) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item1) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item3) - - describe "check boxes for line items", -> - line_item1 = line_item2 = null - - beforeEach -> - line_item1 = { name: "line item 1", checked: false } - line_item2 = { name: "line item 2", checked: false } - scope.filteredLineItems = [ line_item1, line_item2 ] - - it "keeps track of whether all filtered lines items are 'checked' or not", -> - expect(scope.allBoxesChecked()).toEqual false - line_item1.checked = true - expect(scope.allBoxesChecked()).toEqual false - line_item2.checked = true - expect(scope.allBoxesChecked()).toEqual true - line_item1.checked = false - expect(scope.allBoxesChecked()).toEqual false - - it "toggles the 'checked' attribute of all line items based to the value of allBoxesChecked", -> - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual true - line_item1.checked = false - expect(scope.allBoxesChecked()).toEqual false - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual true - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual false - - describe "unit calculations", -> - beforeEach -> - scope.initialiseVariables() - - describe "fulfilled()", -> - it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> - expect(scope.fulfilled()).toEqual '' - - it "returns '' if selectedUnitsVariant has no property 'group_buy_unit_size' or group_buy_unit_size is 0", -> - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 0 } - expect(scope.fulfilled()).toEqual '' - scope.selectedUnitsProduct = { variant_unit: "weight" } - expect(scope.fulfilled()).toEqual '' - - it "returns '', and does not call Math.round if variant_unit is 'items'", -> - spyOn(Math,"round") - scope.selectedUnitsProduct = { variant_unit: "items", group_buy_unit_size: 10 } - expect(scope.fulfilled()).toEqual '' - expect(Math.round).not.toHaveBeenCalled() - - it "calls Math.round() if variant_unit is 'weight' or 'volume'", -> - spyOn(Math,"round") - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 10 } - scope.fulfilled() - expect(Math.round).toHaveBeenCalled() - scope.selectedUnitsProduct = { variant_unit: "volume", group_buy_unit_size: 10 } - scope.fulfilled() - expect(Math.round).toHaveBeenCalled() - - it "returns the quantity of fulfilled group buy units", -> - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 1000 } - expect(scope.fulfilled(1500)).toEqual 1.5 - - describe "allFinalWeightVolumesPresent()", -> - it "returns false if the unit_value of any item in filteredLineItems does not exist", -> - scope.filteredLineItems = [ - { final_weight_volume: 1000 } - { final_weight_volume: 3000 } - { final_weight_yayaya: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - - it "returns false if the unit_value of any item in filteredLineItems is not a number greater than 0", -> - scope.filteredLineItems = [ - { final_weight_volume: 0 } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - scope.filteredLineItems = [ - { final_weight_volume: 'lalala' } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - - it "returns true if the unit_value of all items in filteredLineItems are numbers greater than 0", -> - scope.filteredLineItems = [ - { final_weight_volume: 1000 } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual true - - describe "sumUnitValues()", -> - it "returns the sum of the final_weight_volumes line_items", -> - scope.filteredLineItems = [ - { final_weight_volume: 2 } - { final_weight_volume: 7 } - { final_weight_volume: 21 } - ] - expect(scope.sumUnitValues()).toEqual 30 - - describe "sumMaxUnitValues()", -> - it "returns the sum of the product of unit_value and maxOf(max_quantity,quantity) for specified line_items", -> - scope.filteredLineItems = [ - { units_variant: { unit_value: 1 }, original_quantity: 2, max_quantity: 5 } - { units_variant: { unit_value: 2 }, original_quantity: 3, max_quantity: 1 } - { units_variant: { unit_value: 3 }, original_quantity: 7, max_quantity: 10 } - ] - sp0 = scope.filteredLineItems[0].units_variant.unit_value * Math.max(scope.filteredLineItems[0].original_quantity, scope.filteredLineItems[0].max_quantity) - sp1 = scope.filteredLineItems[1].units_variant.unit_value * Math.max(scope.filteredLineItems[1].original_quantity, scope.filteredLineItems[1].max_quantity) - sp2 = scope.filteredLineItems[2].units_variant.unit_value * Math.max(scope.filteredLineItems[2].original_quantity, scope.filteredLineItems[2].max_quantity) - expect(scope.sumMaxUnitValues()).toEqual (sp0 + sp1 + sp2) - - describe "formatting a value based upon the properties of a specified Units Variant", -> - # A Units Variant is an API object which holds unit properies of a variant - - beforeEach -> - spyOn(Math,"round").andCallThrough() - - it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> - expect(scope.formattedValueWithUnitName(1,{})).toEqual '' - - it "returns '', and does not call Math.round if variant_unit is 'items'", -> - unitsVariant = { variant_unit: "items" } - expect(scope.formattedValueWithUnitName(1,unitsVariant)).toEqual '' - expect(Math.round).not.toHaveBeenCalled() - - it "calls Math.round() if variant_unit is 'weight' or 'volume'", -> - unitsVariant = { variant_unit: "weight" } - scope.formattedValueWithUnitName(1,unitsVariant) - expect(Math.round).toHaveBeenCalled() - scope.selectedUnitsVariant = { variant_unit: "volume" } - scope.formattedValueWithUnitName(1,unitsVariant) - expect(Math.round).toHaveBeenCalled() - - it "calls Math.round with the quotient of scale and value, multiplied by 1000", -> - unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 5 - scope.formattedValueWithUnitName(10, unitsVariant) - expect(Math.round).toHaveBeenCalledWith 10/5 * 1000 - - it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", -> - unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 1000 - spyOn(VariantUnitManager, "getUnitName").andReturn "kg" - expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" - - describe "updating the price upon updating the weight of a line item", -> - it "updates the price if the weight is changed", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: 4000 } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 4.00 - - it "doesn't update the price if the weight <= 0", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: 0 } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 2.00 - - it "doesn't update the price if the weight is an empty string", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: "" } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 2.00 - - describe "updating final_weight_volume upon updating the quantity for a line_item", -> - beforeEach -> - spyOn(scope, "weightAdjustedPrice") - - it "updates the weight if the quantity is changed, then calls weightAdjustedPrice()", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 2, original_final_weight_volume: 2000, final_weight_volume: 0 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 4000 - expect(scope.weightAdjustedPrice).toHaveBeenCalled() - - it "doesn't update the weight if the quantity <= 0", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 0, original_final_weight_volume: 2000, final_weight_volume: 1000 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000 - - it "doesn't update the weight if the quantity is an empty string", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: "", original_final_weight_volume: 2000, final_weight_volume: 1000 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000 - - -describe "Auxiliary functions", -> - describe "getting a zero filled two digit number", -> - it "returns the number as a string if its value is greater than or equal to 10", -> - expect(twoDigitNumber(10)).toEqual "10" - expect(twoDigitNumber(15)).toEqual "15" - expect(twoDigitNumber(99)).toEqual "99" - - it "returns the number formatted as a zero filled string if its value is less than 10", -> - expect(twoDigitNumber(0)).toEqual "00" - expect(twoDigitNumber(1)).toEqual "01" - expect(twoDigitNumber(9)).toEqual "09" - - describe "formatting dates and times", -> - date = null - - beforeEach -> - date = new Date - date.setYear(2010) - date.setMonth(4) # Zero indexed, so 4 is May - date.setDate(15) - date.setHours(5) - date.setMinutes(10) - date.setSeconds(30) - - it "returns a date formatted as yyyy-mm-dd", -> - expect(formatDate(date)).toEqual "2010-05-15" - - it "returns a time formatted as hh-MM:ss", -> - expect(formatTime(date)).toEqual "05:10:30" diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index 1f761418d2..cc0b574f47 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -241,6 +241,7 @@ describe "AdminProductEditCtrl", -> $provide.value "taxons", [] $provide.value "tax_categories", [] $provide.value 'SpreeApiKey', 'API_KEY' + $provide.value 'columns', [] null beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_) -> @@ -258,10 +259,10 @@ describe "AdminProductEditCtrl", -> describe "loading data upon initialisation", -> it "gets a list of producers and then resets products with a list of data", -> $httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" - spyOn($scope, "fetchProducts").andReturn "nothing" + spyOn($scope, "fetchProducts").and.returnValue "nothing" $scope.initialise() $httpBackend.flush() - expect($scope.fetchProducts.calls.length).toEqual 1 + expect($scope.fetchProducts.calls.count()).toBe 1 expect($scope.spree_api_key_ok).toEqual true @@ -277,7 +278,7 @@ describe "AdminProductEditCtrl", -> deferred = $q.defer() deferred.resolve() spyOn $scope, "resetProducts" - spyOn(BulkProducts, "fetch").andReturn deferred.promise + spyOn(BulkProducts, "fetch").and.returnValue deferred.promise it "calls resetProducts after data has been received", -> $scope.fetchProducts() @@ -312,13 +313,13 @@ describe "AdminProductEditCtrl", -> describe "updating the product on hand count", -> it "updates when product is not available on demand", -> - spyOn($scope, "onHand").andReturn 123 + spyOn($scope, "onHand").and.returnValue 123 product = {on_demand: false} $scope.updateOnHand(product) expect(product.on_hand).toEqual 123 it "updates when product's variants are not available on demand", -> - spyOn($scope, "onHand").andReturn 123 + spyOn($scope, "onHand").and.returnValue 123 product = {on_demand: false, variants: [{on_demand: false}]} $scope.updateOnHand(product) expect(product.on_hand).toEqual 123 @@ -610,7 +611,7 @@ describe "AdminProductEditCtrl", -> describe "filtering products", -> beforeEach -> spyOn $scope, "packProduct" - spyOn(window, "filterSubmitProducts").andReturn [ + spyOn(window, "filterSubmitProducts").and.returnValue [ { id: 1 value: 3 @@ -632,7 +633,7 @@ describe "AdminProductEditCtrl", -> $scope.submitProducts() it "packs all products and all dirty products", -> - expect($scope.packProduct.calls.length).toEqual 4 + expect($scope.packProduct.calls.count()).toBe 4 it "filters returned dirty products", -> expect(filterSubmitProducts).toHaveBeenCalledWith @@ -734,7 +735,7 @@ describe "AdminProductEditCtrl", -> describe "deleting products", -> it "deletes products with a http delete request to /api/products/id/soft_delete", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -751,7 +752,7 @@ describe "AdminProductEditCtrl", -> $httpBackend.flush() it "removes the specified product from both $scope.products and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -790,7 +791,7 @@ describe "AdminProductEditCtrl", -> describe "when the variant has not been saved", -> it "removes the variant from products and dirtyProducts", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ {id: 1, variants: [{id: -1},{id: -2}]} ] @@ -806,7 +807,7 @@ describe "AdminProductEditCtrl", -> describe "when the variant has been saved", -> it "deletes variants with a http delete request to /api/products/product_permalink/variants/(variant_id)/soft_delete", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -832,7 +833,7 @@ describe "AdminProductEditCtrl", -> $httpBackend.flush() it "removes the specified variant from both the variants object and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -927,7 +928,7 @@ describe "converting arrays of objects with ids to an object with ids as keys", it "sends arrays with the key 'variants' to itself", -> - spyOn(window, "toObjectWithIDKeys").andCallThrough() + spyOn(window, "toObjectWithIDKeys").and.callThrough() array = [ { id: 1 diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee index 390a7a1c94..d395ae9cc6 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee @@ -30,7 +30,7 @@ describe "CheckoutCtrl", -> beforeEach -> inject ($controller, $rootScope, _storage_) -> storage = _storage_ - spyOn(storage, "bind").andCallThrough() + spyOn(storage, "bind").and.callThrough() scope = $rootScope.$new() CurrentUser = { id: 1 } ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: CurrentUser } diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index d985904f81..ce3fe7eec3 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -131,7 +131,7 @@ describe 'Cart service', -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 5} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 0 expect(li.max_quantity).toBeUndefined() @@ -139,14 +139,14 @@ describe 'Cart service', -> it "reduces the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 6} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.max_quantity).toEqual 0 it "resets the count on hand available", -> li = {variant: {id: 1, count_on_hand: 10}, quantity: 5} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 0 @@ -154,7 +154,7 @@ describe 'Cart service', -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 5}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 5 expect(li.max_quantity).toBeUndefined() @@ -162,14 +162,14 @@ describe 'Cart service', -> it "does not reduce the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 6, max_quantity: 7} stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.max_quantity).toEqual 7 it "resets the count on hand available", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 6}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 6 @@ -177,7 +177,7 @@ describe 'Cart service', -> it "does not reset the quantity", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 6}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 6 expect(li.max_quantity).toBeUndefined() @@ -185,7 +185,7 @@ describe 'Cart service', -> it "does not reset the max_quantity", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 7} stockLevels = {1: {quantity: 5, max_quantity: 6, on_hand: 7}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 5 expect(li.max_quantity).toEqual 7 @@ -193,14 +193,14 @@ describe 'Cart service', -> describe "when the client-side quantity has been changed from 0 to 1 during the request", -> it "does not reset the quantity", -> li = {variant: {id: 1}, quantity: 1} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels {} expect(li.quantity).toEqual 1 expect(li.max_quantity).toBeUndefined() it "does not reset the max_quantity", -> li = {variant: {id: 1}, quantity: 1, max_quantity: 1} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels {} expect(li.quantity).toEqual 1 expect(li.max_quantity).toEqual 1 diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee index 718504a377..43e83409ad 100644 --- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee @@ -30,6 +30,7 @@ describe 'Checkout service', -> beforeEach -> orderData = id: 3102 + shipping_method_id: null payment_method_id: null email: "test@test.com" bill_address: @@ -78,7 +79,7 @@ describe 'Checkout service', -> expect(Checkout.shippingPrice()).toEqual 13 it 'Gets the current payment method', -> - expect(Checkout.paymentMethod()).toEqual null + expect(Checkout.paymentMethod()).toBeUndefined() Checkout.order.payment_method_id = 99 expect(Checkout.paymentMethod()).toEqual paymentMethods[0] diff --git a/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee index 8551113280..6ace586e30 100644 --- a/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee @@ -13,13 +13,13 @@ describe "Sidebar", -> it 'is active when a location in paths is set', -> - spyOn(location, "path").andReturn "/test" + spyOn(location, "path").and.returnValue "/test" expect(Sidebar.active()).toEqual true it 'is inactive if location is set', -> - spyOn(location, "path").andReturn null + spyOn(location, "path").and.returnValue null expect(Sidebar.active()).toEqual false - + describe "Toggling on/off", -> it 'toggles the current sidebar path', -> expect(Sidebar.active()).toEqual false @@ -32,6 +32,3 @@ describe "Sidebar", -> spyOn(Navigation, 'navigate') Sidebar.toggle() expect(Navigation.navigate).toHaveBeenCalledWith("/test") - - - diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 973b348383..bbbff7d82b 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -13,10 +13,10 @@ describe 'OrderCycle controllers', -> event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = - exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected') - productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied') - variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied') - exchangeDirection: jasmine.createSpy('exchangeDirection').andReturn('exchange direction') + exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').and.returnValue('variants selected') + productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').and.returnValue('product supplied') + variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').and.returnValue('variant supplied') + exchangeDirection: jasmine.createSpy('exchangeDirection').and.returnValue('exchange direction') toggleProducts: jasmine.createSpy('toggleProducts') setExchangeVariants: jasmine.createSpy('setExchangeVariants') addSupplier: jasmine.createSpy('addSupplier') @@ -28,15 +28,15 @@ describe 'OrderCycle controllers', -> removeExchangeFee: jasmine.createSpy('removeExchangeFee') removeDistributionOfVariant: jasmine.createSpy('removeDistributionOfVariant') create: jasmine.createSpy('create') - new: jasmine.createSpy('new').andReturn "my order cycle" + new: jasmine.createSpy('new').and.returnValue "my order cycle" Enterprise = - index: jasmine.createSpy('index').andReturn('enterprises list') + index: jasmine.createSpy('index').and.returnValue('enterprises list') supplied_products: 'supplied products' - suppliedVariants: jasmine.createSpy('suppliedVariants').andReturn('supplied variants') - totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total') + suppliedVariants: jasmine.createSpy('suppliedVariants').and.returnValue('supplied variants') + totalVariants: jasmine.createSpy('totalVariants').and.returnValue('variants total') EnterpriseFee = - index: jasmine.createSpy('index').andReturn('enterprise fees list') - forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise') + index: jasmine.createSpy('index').and.returnValue('enterprise fees list') + forEnterprise: jasmine.createSpy('forEnterprise').and.returnValue('enterprise fees for enterprise') ocInstance = {} module('admin.orderCycles') @@ -58,12 +58,13 @@ describe 'OrderCycle controllers', -> describe 'Reporting when all resources are loaded', -> it 'returns true when Enterprise and EnterpriseFee are loaded', -> - Enterprise.loaded = EnterpriseFee.loaded = true + Enterprise.loaded = EnterpriseFee.loaded = OrderCycle.loaded = true expect(scope.loaded()).toBe(true) it 'returns false otherwise', -> Enterprise.loaded = true EnterpriseFee.loaded = false + OrderCycle.loaded = true expect(scope.loaded()).toBe(false) it "delegates suppliedVariants to Enterprise", -> @@ -99,7 +100,7 @@ describe 'OrderCycle controllers', -> 1: {id: 1, name: 'Eaterprises'} 2: {id: 2, name: 'Pepper Tree Place'} 3: {id: 3, name: 'South East'} - OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').andReturn([2]) + OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').and.returnValue([2]) EnterpriseFee.enterprise_fees = [ {enterprise_id: 2} ] # Pepper Tree Place has a fee expect(scope.enterprisesWithFees()).toEqual([ {id: 2, name: 'Pepper Tree Place'} @@ -156,7 +157,9 @@ describe 'OrderCycle controllers', -> expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('variant') it 'Submits the order cycle via OrderCycle create', -> - scope.submit('/admin/order_cycles') + eventMock = {preventDefault: jasmine.createSpy()} + scope.submit(eventMock,'/admin/order_cycles') + expect(eventMock.preventDefault).toHaveBeenCalled() expect(OrderCycle.create).toHaveBeenCalledWith('/admin/order_cycles') describe 'AdminEditOrderCycleCtrl', -> @@ -169,7 +172,9 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = + order_cycle_form: jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine']) + $watch: jasmine.createSpy('$watch') event = preventDefault: jasmine.createSpy('preventDefault') location = @@ -177,10 +182,10 @@ describe 'OrderCycle controllers', -> 'example.com/admin/order_cycles/27/edit' OrderCycle = load: jasmine.createSpy('load') - exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected') - productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied') - variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied') - exchangeDirection: jasmine.createSpy('exchangeDirection').andReturn('exchange direction') + exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').and.returnValue('variants selected') + productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').and.returnValue('product supplied') + variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').and.returnValue('variant supplied') + exchangeDirection: jasmine.createSpy('exchangeDirection').and.returnValue('exchange direction') toggleProducts: jasmine.createSpy('toggleProducts') setExchangeVariants: jasmine.createSpy('setExchangeVariants') addSupplier: jasmine.createSpy('addSupplier') @@ -193,13 +198,13 @@ describe 'OrderCycle controllers', -> removeDistributionOfVariant: jasmine.createSpy('removeDistributionOfVariant') update: jasmine.createSpy('update') Enterprise = - index: jasmine.createSpy('index').andReturn('enterprises list') + index: jasmine.createSpy('index').and.returnValue('enterprises list') supplied_products: 'supplied products' - suppliedVariants: jasmine.createSpy('suppliedVariants').andReturn('supplied variants') - totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total') + suppliedVariants: jasmine.createSpy('suppliedVariants').and.returnValue('supplied variants') + totalVariants: jasmine.createSpy('totalVariants').and.returnValue('variants total') EnterpriseFee = - index: jasmine.createSpy('index').andReturn('enterprise fees list') - forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise') + index: jasmine.createSpy('index').and.returnValue('enterprise fees list') + forEnterprise: jasmine.createSpy('forEnterprise').and.returnValue('enterprise fees for enterprise') module('admin.orderCycles') inject ($controller) -> @@ -261,7 +266,7 @@ describe 'OrderCycle controllers', -> 1: {id: 1, name: 'Eaterprises'} 2: {id: 2, name: 'Pepper Tree Place'} 3: {id: 3, name: 'South East'} - OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').andReturn([2]) + OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').and.returnValue([2]) EnterpriseFee.enterprise_fees = [ {enterprise_id: 2} ] # Pepper Tree Place has a fee expect(scope.enterprisesWithFees()).toEqual([ {id: 2, name: 'Pepper Tree Place'} @@ -292,6 +297,7 @@ describe 'OrderCycle controllers', -> scope.removeExchange(event, 'exchange') expect(event.preventDefault).toHaveBeenCalled() expect(OrderCycle.removeExchange).toHaveBeenCalledWith('exchange') + expect(scope.order_cycle_form.$dirty).toEqual true it 'Adds coordinator fees', -> scope.addCoordinatorFee(event) @@ -318,8 +324,10 @@ describe 'OrderCycle controllers', -> expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('variant') it 'Submits the order cycle via OrderCycle update', -> - scope.submit('/admin/order_cycles') - expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') + eventMock = {preventDefault: jasmine.createSpy()} + scope.submit(eventMock,'/admin/order_cycles') + expect(eventMock.preventDefault).toHaveBeenCalled() + expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles', scope.order_cycle_form) describe 'OrderCycle services', -> @@ -332,7 +340,7 @@ describe 'OrderCycle services', -> inject ($injector, _$httpBackend_)-> Enterprise = $injector.get('Enterprise') $httpBackend = _$httpBackend_ - $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json?').respond [ + $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json').respond [ {id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true} {id: 2, name: 'Two', supplied_products: [3, 4]} {id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'} @@ -368,7 +376,7 @@ describe 'OrderCycle services', -> expect(Enterprise.supplied_products).toEqual [1, 2, 3, 4, 5, 6] it "finds supplied variants for an enterprise", -> - spyOn(Enterprise, 'variantsOf').andReturn(10) + spyOn(Enterprise, 'variantsOf').and.returnValue(10) Enterprise.index() $httpBackend.flush() expect(Enterprise.suppliedVariants(1)).toEqual [10, 10] @@ -408,7 +416,7 @@ describe 'OrderCycle services', -> inject ($injector, _$httpBackend_)-> EnterpriseFee = $injector.get('EnterpriseFee') $httpBackend = _$httpBackend_ - $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json?').respond [ + $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json').respond [ {id: 1, name: "Yayfee", enterprise_id: 1} {id: 2, name: "FeeTwo", enterprise_id: 2} ] @@ -713,7 +721,7 @@ describe 'OrderCycle services', -> master_id: 6 variants: [{id: 7}, {id: 8}] - spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3]) + spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) it 'returns true for products whose master is supplied', -> expect(OrderCycle.productSuppliedToOrderCycle(product_master_present)).toBeTruthy() @@ -730,7 +738,7 @@ describe 'OrderCycle services', -> describe 'checking whether a variant is supplied to the order cycle', -> beforeEach -> - spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3]) + spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) it 'returns true for variants that are supplied', -> expect(OrderCycle.variantSuppliedToOrderCycle({id: 1})).toBeTruthy() @@ -806,11 +814,11 @@ describe 'OrderCycle services', -> describe 'creating an order cycle', -> beforeEach -> - spyOn(OrderCycle, 'confirmNoDistributors').andReturn true + spyOn(OrderCycle, 'confirmNoDistributors').and.returnValue true it 'redirects to the destination page on success', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPOST('/admin/order_cycles.json', { order_cycle: 'this is the submit data' }).respond {success: true} @@ -821,7 +829,7 @@ describe 'OrderCycle services', -> it 'does not redirect on error', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPOST('/admin/order_cycles.json', { order_cycle: 'this is the submit data' }).respond {success: false} @@ -832,22 +840,24 @@ describe 'OrderCycle services', -> describe 'updating an order cycle', -> beforeEach -> - spyOn(OrderCycle, 'confirmNoDistributors').andReturn true + spyOn(OrderCycle, 'confirmNoDistributors').and.returnValue true it 'redirects to the destination page on success', -> + form = jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine']) OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPUT('/admin/order_cycles.json?reloading=1', { order_cycle: 'this is the submit data' }).respond {success: true} - OrderCycle.update('/destination/page') + OrderCycle.update('/destination/page', form) $httpBackend.flush() expect($window.location).toEqual('/destination/page') + expect(form.$setPristine.calls.count()).toBe 1 it 'does not redirect on error', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPUT('/admin/order_cycles.json?reloading=1', { order_cycle: 'this is the submit data' }).respond {success: false} @@ -960,19 +970,19 @@ describe 'OrderCycle services', -> outgoing_exchanges: [] it "returns true when there are distributors", -> - spyOn window, 'confirm' + spyOn(window, 'confirm') OrderCycle.order_cycle = order_cycle_with_exchanges expect(OrderCycle.confirmNoDistributors()).toBe true expect(window.confirm).not.toHaveBeenCalled() it "returns true when there are no distributors but the user confirms", -> - spyOn(window, 'confirm').andReturn true + spyOn(window, 'confirm').and.returnValue(true) OrderCycle.order_cycle = order_cycle_without_exchanges expect(OrderCycle.confirmNoDistributors()).toBe true expect(window.confirm).toHaveBeenCalled() it "returns false when there are no distributors and the user does not confirm", -> - spyOn(window, 'confirm').andReturn false + spyOn(window, 'confirm').and.returnValue(false) OrderCycle.order_cycle = order_cycle_without_exchanges expect(OrderCycle.confirmNoDistributors()).toBe false expect(window.confirm).toHaveBeenCalled() diff --git a/spec/jobs/update_account_invoices_spec.rb b/spec/jobs/update_account_invoices_spec.rb index 87d601b60a..ad1a466d49 100644 --- a/spec/jobs/update_account_invoices_spec.rb +++ b/spec/jobs/update_account_invoices_spec.rb @@ -12,6 +12,7 @@ describe UpdateAccountInvoices do Spree::Config.set(:account_invoices_monthly_fixed, 5) Spree::Config.set(:account_invoices_monthly_rate, 0.02) Spree::Config.set(:account_invoices_monthly_cap, 50) + Spree::Config.set(:minimum_billable_turnover, 0) end describe "units specs" do diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 0c7e82b89d..7291714e03 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -2,29 +2,34 @@ require 'spec_helper' require 'yaml' describe ProducerMailer do + let!(:zone) { create(:zone_with_member) } + let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, zone: zone, amount: 0.1) } + let!(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) } let(:s1) { create(:supplier_enterprise) } let(:s2) { create(:supplier_enterprise) } let(:s3) { create(:supplier_enterprise) } - let(:d1) { create(:distributor_enterprise) } + let(:d1) { create(:distributor_enterprise, charges_sales_tax: true) } let(:d2) { create(:distributor_enterprise) } - let(:p1) { create(:product, price: 12.34, supplier: s1) } + let(:p1) { create(:product, price: 12.34, supplier: s1, tax_category: tax_category) } let(:p2) { create(:product, price: 23.45, supplier: s2) } let(:p3) { create(:product, price: 34.56, supplier: s1) } + let(:p4) { create(:product, price: 45.67, supplier: s1) } let(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { order_cycle.exchanges.create! sender: s1, receiver: d1, incoming: true, receival_instructions: 'Outside shed.' } let!(:order) do order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete') - order.line_items << create(:line_item, variant: p1.master) - order.line_items << create(:line_item, variant: p1.master) - order.line_items << create(:line_item, variant: p2.master) + order.line_items << create(:line_item, quantity: 1, variant: p1.variants.first) + order.line_items << create(:line_item, quantity: 2, variant: p1.variants.first) + order.line_items << create(:line_item, quantity: 3, variant: p2.variants.first) + order.line_items << create(:line_item, quantity: 2, variant: p4.variants.first) order.finalize! order.save order end let!(:order_incomplete) do order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'payment') - order.line_items << create(:line_item, variant: p3.master) + order.line_items << create(:line_item, variant: p3.variants.first) order.save order end @@ -44,7 +49,7 @@ describe ProducerMailer do end it "includes receival instructions" do - mail.body.should include 'Outside shed.' + mail.body.encoded.should include 'Outside shed.' end it "cc's the enterprise" do @@ -53,13 +58,28 @@ describe ProducerMailer do it "contains an aggregated list of produce" do body_lines_including(mail, p1.name).each do |line| - line.should include 'QTY: 2' - line.should include '@ $10.00 = $20.00' + line.should include 'QTY: 3' + line.should include '@ $10.00 = $30.00' end + body_as_html(mail).find("table.order-summary tr", text: p1.name) + .should have_selector("td", text: "$30.00") + end + + it "displays tax totals for each product" do + # Tax for p1 line items + body_as_html(mail).find("table.order-summary tr", text: p1.name) + .should have_selector("td.tax", text: "$2.73") end it "does not include incomplete orders" do - mail.body.should_not include p3.name + mail.body.encoded.should_not include p3.name + end + + it "includes the total" do + # puts mail.text_part.body.encoded + mail.body.encoded.should include 'Total: $50.00' + body_as_html(mail).find("tr.total-row") + .should have_selector("td", text: "$50.00") end it "sends no mail when the producer has no orders" do @@ -74,4 +94,8 @@ describe ProducerMailer do def body_lines_including(mail, s) mail.body.to_s.lines.select { |line| line.include? s } end + + def body_as_html(mail) + Capybara.string(mail.html_part.body.encoded) + end end diff --git a/spec/models/billable_period_spec.rb b/spec/models/billable_period_spec.rb index 3037ebc6bc..7095080920 100644 --- a/spec/models/billable_period_spec.rb +++ b/spec/models/billable_period_spec.rb @@ -27,6 +27,73 @@ describe BillablePeriod, type: :model do end end + describe "calculating monthly bills for enterprises with no turnover" do + let!(:subject) { create(:billable_period, turnover: 0) } + + context "when no tax is charged" do + before { Spree::Config.set(:account_invoices_tax_rate, 0) } + + context "when minimum billable turnover is zero" do + before { Spree::Config.set(:minimum_billable_turnover, 0) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + it { expect(subject.bill).to eq 10 } + end + + context "when no fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when minimum billable turnover is > zero" do + before { Spree::Config.set(:minimum_billable_turnover, 1) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + it { expect(subject.bill).to eq 0 } + end + + context "when no fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + + context "when tax is charged" do + before { Spree::Config.set(:account_invoices_tax_rate, 0.1) } + + context "when minimum billable turnover is zero" do + before { Spree::Config.set(:minimum_billable_turnover, 0) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + it { expect(subject.bill).to eq 11 } + end + + context "when no fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when minimum billable turnover is > zero" do + before { Spree::Config.set(:minimum_billable_turnover, 1) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + it { expect(subject.bill).to eq 0 } + end + + context "when no fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + end describe "calculating monthly bills for enterprises" do let!(:subject) { create(:billable_period, turnover: 100) } @@ -34,94 +101,371 @@ describe BillablePeriod, type: :model do context "when no tax is charged" do before { Spree::Config.set(:account_invoices_tax_rate, 0) } - context "when a fixed cost is included" do - before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + context "when no minimum billable turnover" do + before { Spree::Config.set(:minimum_billable_turnover, 0) } - context "when a percentage of turnover is included" do - before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } - context "when the bill is capped" do - context "at a level higher than the fixed charge plus the product of the rate and turnover" do - before { Spree::Config.set(:account_invoices_monthly_cap, 65) } - it { expect(subject.bill).to eq 60 } + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 65) } + it { expect(subject.bill).to eq 60 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 55 } + end end - context "at a level lower than the fixed charge plus the product of the rate and turnover" do - before { Spree::Config.set(:account_invoices_monthly_cap, 55) } - it { expect(subject.bill).to eq 55 } + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 60 } end end - context "when the bill is not capped" do - before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 60 } + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 15) } + it { expect(subject.bill).to eq 10 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 5) } + it { expect(subject.bill).to eq 5 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 10 } + end end end - context "when a percentage of turnover is not included" do - before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } - context "when the bill is capped" do - context "at a level higher than the fixed charge" do - before { Spree::Config.set(:account_invoices_monthly_cap, 15) } - it { expect(subject.bill).to eq 10 } + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 50 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 45) } + it { expect(subject.bill).to eq 45 } + end end - context "at a level lower than the fixed charge" do - before { Spree::Config.set(:account_invoices_monthly_cap, 5) } - it { expect(subject.bill).to eq 5 } + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 50 } end end - context "when the bill is not capped" do - before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 10 } + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end end end end - context "when a fixed cost is not included" do - before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + context "when turnover is above minimum billable turnover" do + before { Spree::Config.set(:minimum_billable_turnover, 99) } - context "when a percentage of turnover is included" do - before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } - context "when the bill is capped" do - context "at a level higher than the product of the rate and turnover" do - before { Spree::Config.set(:account_invoices_monthly_cap, 55) } - it { expect(subject.bill).to eq 50 } + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 65) } + it { expect(subject.bill).to eq 60 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 55 } + end end - context "at a level lower than the product of the rate and turnover" do - before { Spree::Config.set(:account_invoices_monthly_cap, 45) } - it { expect(subject.bill).to eq 45 } + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 60 } end end - context "when the bill is not capped" do - before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 50 } + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 15) } + it { expect(subject.bill).to eq 10 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 5) } + it { expect(subject.bill).to eq 5 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 10 } + end end end - context "when a percentage of turnover is not included" do - before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } - context "when the bill is capped" do - before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(subject.bill).to eq 0 } + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 50 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 45) } + it { expect(subject.bill).to eq 45 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 50 } + end end - context "when the bill is not capped" do - before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 0 } + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + end + + context "when turnover is below minimum billable turnover" do + before { Spree::Config.set(:minimum_billable_turnover, 101) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 65) } + it { expect(subject.bill).to eq 0 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 15) } + it { expect(subject.bill).to eq 0 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 5) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 0 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 45) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + end + + context "when tax is charged" do + before { Spree::Config.set(:account_invoices_tax_rate, 0.1) } + + context "when turnover is above minimum billable turnover" do + before { Spree::Config.set(:minimum_billable_turnover, 99) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 61) } + it { expect(subject.bill).to eq 66 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 59) } + it { + expect(subject.bill.to_f).to eq 64.9 + } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 66 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 11) } + it { expect(subject.bill).to eq 11 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 9) } + it { expect(subject.bill.to_f).to eq 9.9 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 11 } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 51) } + it { expect(subject.bill).to eq 55 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 49) } + it { expect(subject.bill.to_f).to eq 53.9 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 55 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end end end end end - context "when tax is charged" do - before { Spree::Config.set(:account_invoices_tax_rate, 0.1) } + context "when turnover is below minimum billable turnover" do + before { Spree::Config.set(:minimum_billable_turnover, 101) } context "when a fixed cost is included" do before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } @@ -132,20 +476,20 @@ describe BillablePeriod, type: :model do context "when the bill is capped" do context "at a level higher than the fixed charge plus the product of the rate and turnover" do before { Spree::Config.set(:account_invoices_monthly_cap, 61) } - it { expect(subject.bill).to eq 66 } + it { expect(subject.bill).to eq 0 } end context "at a level lower than the fixed charge plus the product of the rate and turnover" do before { Spree::Config.set(:account_invoices_monthly_cap, 59) } it { - expect(subject.bill.to_f).to eq 64.9 + expect(subject.bill.to_f).to eq 0 } end end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 66 } + it { expect(subject.bill).to eq 0 } end end @@ -155,18 +499,18 @@ describe BillablePeriod, type: :model do context "when the bill is capped" do context "at a level higher than the fixed charge" do before { Spree::Config.set(:account_invoices_monthly_cap, 11) } - it { expect(subject.bill).to eq 11 } + it { expect(subject.bill).to eq 0 } end context "at a level lower than the fixed charge" do before { Spree::Config.set(:account_invoices_monthly_cap, 9) } - it { expect(subject.bill.to_f).to eq 9.9 } + it { expect(subject.bill.to_f).to eq 0 } end end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 11 } + it { expect(subject.bill).to eq 0 } end end end @@ -180,18 +524,18 @@ describe BillablePeriod, type: :model do context "when the bill is capped" do context "at a level higher than the product of the rate and turnover" do before { Spree::Config.set(:account_invoices_monthly_cap, 51) } - it { expect(subject.bill).to eq 55 } + it { expect(subject.bill).to eq 0 } end context "at a level lower than the product of the rate and turnover" do before { Spree::Config.set(:account_invoices_monthly_cap, 49) } - it { expect(subject.bill.to_f).to eq 53.9 } + it { expect(subject.bill.to_f).to eq 0 } end end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(subject.bill).to eq 55 } + it { expect(subject.bill).to eq 0 } end end diff --git a/spec/models/column_preference_spec.rb b/spec/models/column_preference_spec.rb new file mode 100644 index 0000000000..4937bf1d34 --- /dev/null +++ b/spec/models/column_preference_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe ColumnPreference, type: :model do + describe "finding stored preferences for a user and action" do + before do + allow(ColumnPreference).to receive(:known_actions) { ['some_action'] } + allow(ColumnPreference).to receive(:valid_columns_for) { ['col1', 'col2', 'col3'] } + end + + let(:user) { create(:user) } + let!(:col1_pref) { ColumnPreference.create(user_id: user.id, action_name: 'some_action', column_name: 'col1', visible: true) } + let!(:col2_pref) { ColumnPreference.create(user_id: user.id, action_name: 'some_action', column_name: 'col2', visible: false) } + let(:defaults) { { + col1: { name: "col1", visible: false }, + col2: { name: "col2", visible: true }, + col3: { name: "col3", visible: false }, + } } + + context "when the user has preferences stored for the given action" do + before do + allow(ColumnPreference).to receive(:some_action_columns) { defaults } + end + + let(:preferences) { ColumnPreference.for(user, :some_action)} + + it "builds an entry for each column listed in the defaults" do + expect(preferences.count).to eq 3 + end + + it "uses values from stored preferences where present" do + expect(preferences).to include col1_pref, col2_pref + end + + it "uses defaults where no stored preference exists" do + default_pref = preferences.last + expect(default_pref).to be_a_new ColumnPreference + expect(default_pref.visible).to be false # As per default + end + end + + context "where the user does not have preferences stored for the given action" do + before do + allow(ColumnPreference).to receive(:some_action_columns) { defaults } + end + + let(:preferences) { ColumnPreference.for(create(:user), :some_action)} + + it "builds an entry for each column listed in the defaults" do + expect(preferences.count).to eq 3 + end + + it "uses defaults where no stored preference exists" do + expect(preferences.all?(&:new_record?)).to be true + expect(preferences.map(&:column_name)).to eq [:col1, :col2, :col3] + expect(preferences.map(&:visible)).to eq [false, true, false] + end + end + end +end diff --git a/spec/serializers/admin/customer_serializer_spec.rb b/spec/serializers/admin/customer_serializer_spec.rb index d63f00d4aa..b8e43f5fcc 100644 --- a/spec/serializers/admin/customer_serializer_spec.rb +++ b/spec/serializers/admin/customer_serializer_spec.rb @@ -3,7 +3,8 @@ describe Api::Admin::CustomerSerializer do let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") } it "serializes a customer" do - serializer = Api::Admin::CustomerSerializer.new customer + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: customer.enterprise_id)) + serializer = Api::Admin::CustomerSerializer.new customer, tag_rule_mapping: tag_rule_mapping result = JSON.parse(serializer.to_json) expect(result['email']).to eq customer.email tags = result['tags'] diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 70c803709e..23f9757c5b 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -29,7 +29,7 @@ module WebHelper def current_path_should_be path current_path = URI.parse(current_url).path - current_path.should == path + expect(page).to have_current_path path end def fill_in_fields(field_values) diff --git a/vendor/assets/javascripts/textAngular-rangy.min.js b/vendor/assets/javascripts/textAngular-rangy.min.js new file mode 100644 index 0000000000..d2de46f10e --- /dev/null +++ b/vendor/assets/javascripts/textAngular-rangy.min.js @@ -0,0 +1,2 @@ +!function(a,b){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"object"==typeof exports?module.exports=a():b.rangy=a()}(function(){function a(a,b){var c=typeof a[b];return c==u||!(c!=t||!a[b])||"unknown"==c}function b(a,b){return!(typeof a[b]!=t||!a[b])}function c(a,b){return typeof a[b]!=v}function d(a){return function(b,c){for(var d=c.length;d--;)if(!a(b,c[d]))return!1;return!0}}function e(a){return a&&A(a,z)&&C(a,y)}function f(a){return b(a,"body")?a.body:a.getElementsByTagName("body")[0]}function g(b){typeof console!=v&&a(console,"log")&&console.log(b)}function h(a,b){F&&b?alert(a):g(a)}function i(a){H.initialized=!0,H.supported=!1,h("Rangy is not supported in this environment. Reason: "+a,H.config.alertOnFail)}function j(a){h("Rangy warning: "+a,H.config.alertOnWarn)}function k(a){return a.message||a.description||String(a)}function l(){if(F&&!H.initialized){var b,c=!1,d=!1;a(document,"createRange")&&(b=document.createRange(),A(b,x)&&C(b,w)&&(c=!0));var h=f(document);if(!h||"body"!=h.nodeName.toLowerCase())return void i("No body element found");if(h&&a(h,"createTextRange")&&(b=h.createTextRange(),e(b)&&(d=!0)),!c&&!d)return void i("Neither Range nor TextRange are available");H.initialized=!0,H.features={implementsDomRange:c,implementsTextRange:d};var j,l;for(var m in E)(j=E[m])instanceof p&&j.init(j,H);for(var n=0,o=K.length;o>n;++n)try{K[n](H)}catch(q){l="Rangy init listener threw an exception. Continuing. Detail: "+k(q),g(l)}}}function m(a,b,c){c&&(a+=" in module "+c.name),H.warn("DEPRECATED: "+a+" is deprecated. Please use "+b+" instead.")}function n(a,b,c,d){a[b]=function(){return m(b,c,d),a[c].apply(a,G.toArray(arguments))}}function o(a){a=a||window,l();for(var b=0,c=L.length;c>b;++b)L[b](a)}function p(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function q(a,b,c){var d=new p(a,b,function(b){if(!b.initialized){b.initialized=!0;try{c(H,b),b.supported=!0}catch(d){var e="Module '"+a+"' failed to load: "+k(d);g(e),d.stack&&g(d.stack)}}});return E[a]=d,d}function r(){}function s(){}var t="object",u="function",v="undefined",w=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],x=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],y=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],z=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],A=d(a),B=d(b),C=d(c),D=[].forEach?function(a,b){a.forEach(b)}:function(a,b){for(var c=0,d=a.length;d>c;++c)b(a[c],c)},E={},F=typeof window!=v&&typeof document!=v,G={isHostMethod:a,isHostObject:b,isHostProperty:c,areHostMethods:A,areHostObjects:B,areHostProperties:C,isTextRange:e,getBody:f,forEach:D},H={version:"1.3.0",initialized:!1,isBrowser:F,supported:!0,util:G,features:{},modules:E,config:{alertOnFail:!1,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==v?!0:rangyAutoInitialize}};H.fail=i,H.warn=j;var I;({}).hasOwnProperty?(G.extend=I=function(a,b,c){var d,e;for(var f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&null!==d&&"object"==typeof d&&null!==e&&"object"==typeof e&&I(d,e,!0),a[f]=e);return b.hasOwnProperty("toString")&&(a.toString=b.toString),a},G.createOptions=function(a,b){var c={};return I(c,b),a&&I(c,a),c}):i("hasOwnProperty not supported"),F||i("Rangy can only run in a browser"),function(){var a;if(F){var b=document.createElement("div");b.appendChild(document.createElement("span"));var c=[].slice;try{1==c.call(b.childNodes,0)[0].nodeType&&(a=function(a){return c.call(a,0)})}catch(d){}}a||(a=function(a){for(var b=[],c=0,d=a.length;d>c;++c)b[c]=a[c];return b}),G.toArray=a}();var J;F&&(a(document,"addEventListener")?J=function(a,b,c){a.addEventListener(b,c,!1)}:a(document,"attachEvent")?J=function(a,b,c){a.attachEvent("on"+b,c)}:i("Document does not have required addEventListener or attachEvent method"),G.addListener=J);var K=[];G.deprecationNotice=m,G.createAliasForDeprecatedMethod=n,H.init=l,H.addInitListener=function(a){H.initialized?a(H):K.push(a)};var L=[];H.addShimListener=function(a){L.push(a)},F&&(H.shim=H.createMissingNativeApi=o,n(H,"createMissingNativeApi","shim")),p.prototype={init:function(){for(var a,b,c=this.dependencies||[],d=0,e=c.length;e>d;++d){if(b=c[d],a=E[b],!(a&&a instanceof p))throw new Error("required module '"+b+"' not found");if(a.init(),!a.supported)throw new Error("required module '"+b+"' not supported")}this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,new Error(a)},warn:function(a){H.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){H.warn("DEPRECATED: "+a+" in module "+this.name+" is deprecated. Please use "+b+" instead")},createError:function(a){return new Error("Error in Rangy "+this.name+" module: "+a)}},H.createModule=function(a){var b,c;2==arguments.length?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]);var d=q(a,c,b);H.initialized&&H.supported&&d.init()},H.createCoreModule=function(a,b,c){q(a,b,c)},H.RangePrototype=r,H.rangePrototype=new r,H.selectionPrototype=new s,H.createCoreModule("DomUtil",[],function(a,b){function c(a){var b;return typeof a.namespaceURI==F||null===(b=a.namespaceURI)||"http://www.w3.org/1999/xhtml"==b}function d(a){var b=a.parentNode;return 1==b.nodeType?b:null}function e(a){for(var b=0;a=a.previousSibling;)++b;return b}function f(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function g(a,b){var c,d=[];for(c=a;c;c=c.parentNode)d.push(c);for(c=b;c;c=c.parentNode)if(K(d,c))return c;return null}function h(a,b,c){for(var d=c?b:b.parentNode;d;){if(d===a)return!0;d=d.parentNode}return!1}function i(a,b){return h(a,b,!0)}function j(a,b,c){for(var d,e=c?a:a.parentNode;e;){if(d=e.parentNode,d===b)return e;e=d}return null}function k(a){var b=a.nodeType;return 3==b||4==b||8==b}function l(a){if(!a)return!1;var b=a.nodeType;return 3==b||8==b}function m(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}function n(a,b,c){var d=a.cloneNode(!1);if(d.deleteData(0,b),a.deleteData(b,a.length-b),m(d,a),c)for(var f,g=0;f=c[g++];)f.node==a&&f.offset>b?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>e(a)&&++f.offset;return d}function o(a){if(9==a.nodeType)return a;if(typeof a.ownerDocument!=F)return a.ownerDocument;if(typeof a.document!=F)return a.document;if(a.parentNode)return o(a.parentNode);throw b.createError("getDocument: no document found for node")}function p(a){var c=o(a);if(typeof c.defaultView!=F)return c.defaultView;if(typeof c.parentWindow!=F)return c.parentWindow;throw b.createError("Cannot get a window object for node")}function q(a){if(typeof a.contentDocument!=F)return a.contentDocument;if(typeof a.contentWindow!=F)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function r(a){if(typeof a.contentWindow!=F)return a.contentWindow;if(typeof a.contentDocument!=F)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function s(a){return a&&G.isHostMethod(a,"setTimeout")&&G.isHostObject(a,"document")}function t(a,b,c){var d;if(a?G.isHostProperty(a,"nodeType")?d=1==a.nodeType&&"iframe"==a.tagName.toLowerCase()?q(a):o(a):s(a)&&(d=a.document):d=document,!d)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return d}function u(a){for(var b;b=a.parentNode;)a=b;return a}function v(a,c,d,f){var h,i,k,l,m;if(a==d)return c===f?0:f>c?-1:1;if(h=j(d,a,!0))return c<=e(h)?-1:1;if(h=j(a,d,!0))return e(h)[index:"+e(a)+",length:"+a.childNodes.length+"]["+(a.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return a.nodeName}function y(a){for(var b,c=o(a).createDocumentFragment();b=a.firstChild;)c.appendChild(b);return c}function z(a,b,c){var d=H(a),e=a.createElement("div");e.contentEditable=""+!!c,b&&(e.innerHTML=b);var f=d.firstChild;return f?d.insertBefore(e,f):d.appendChild(e),e}function A(a){return a.parentNode.removeChild(a)}function B(a){this.root=a,this._next=a}function C(a){return new B(a)}function D(a,b){this.node=a,this.offset=b}function E(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var F="undefined",G=a.util,H=G.getBody;G.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),G.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var I=document.createElement("div");G.areHostMethods(I,["insertBefore","appendChild","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),G.isHostProperty(I,"innerHTML")||b.fail("Element is missing innerHTML property");var J=document.createTextNode("test");G.areHostMethods(J,["splitText","deleteData","insertData","appendData","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"])||!G.areHostProperties(J,["data"]))||b.fail("Incomplete Text Node implementation");var K=function(a,b){for(var c=a.length;c--;)if(a[c]===b)return!0;return!1},L=!1;!function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="
    ",L=w(c),a.features.crashyTextNodes=L}();var M;typeof window.getComputedStyle!=F?M=function(a,b){return p(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=F?M=function(a,b){return a.currentStyle?a.currentStyle[b]:""}:b.fail("No means of obtaining computed style properties found"),B.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a,b,c=this._current=this._next;if(this._current)if(a=c.firstChild)this._next=a;else{for(b=null;c!==this.root&&!(b=c.nextSibling);)c=c.parentNode;this._next=b}return this._current},detach:function(){this._current=this._next=this.root=null}},D.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+x(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},E.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11,INVALID_NODE_TYPE_ERR:24},E.prototype.toString=function(){return this.message},a.dom={arrayContains:K,isHtmlNamespace:c,parentElement:d,getNodeIndex:e,getNodeLength:f,getCommonAncestor:g,isAncestorOf:h,isOrIsAncestorOf:i,getClosestAncestorIn:j,isCharacterDataNode:k,isTextOrCommentNode:l,insertAfter:m,splitDataNode:n,getDocument:o,getWindow:p,getIframeWindow:r,getIframeDocument:q,getBody:H,isWindow:s,getContentDocument:t,getRootContainer:u,comparePoints:v,isBrokenNode:w,inspectNode:x,getComputedStyleProperty:M,createTestElement:z,removeNode:A,fragmentFromNodeChildren:y,createIterator:C,DomPosition:D},a.DOMException=E}),H.createCoreModule("DomRange",["DomUtil"],function(a,b){function c(a,b){return 3!=a.nodeType&&(P(a,b.startContainer)||P(a,b.endContainer))}function d(a){return a.document||Q(a.startContainer)}function e(a){return W(a.startContainer)}function f(a){return new L(a.parentNode,O(a))}function g(a){return new L(a.parentNode,O(a)+1)}function h(a,b,c){var d=11==a.nodeType?a.firstChild:a;return N(b)?c==b.length?J.insertAfter(a,b):b.parentNode.insertBefore(a,0==c?b:S(b,c)):c>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[c]),d}function i(a,b,c){if(z(a),z(b),d(b)!=d(a))throw new M("WRONG_DOCUMENT_ERR");var e=R(a.startContainer,a.startOffset,b.endContainer,b.endOffset),f=R(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?0>=e&&f>=0:0>e&&f>0}function j(a){for(var b,c,e,f=d(a.range).createDocumentFragment();c=a.next();){if(b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(j(e)),e.detach()),10==c.nodeType)throw new M("HIERARCHY_REQUEST_ERR");f.appendChild(c)}return f}function k(a,b,c){var d,e;c=c||{stop:!1};for(var f,g;f=a.next();)if(a.isPartiallySelectedSubtree()){if(b(f)===!1)return void(c.stop=!0);if(g=a.getSubtreeIterator(),k(g,b,c),g.detach(),c.stop)return}else for(d=J.createIterator(f);e=d.next();)if(b(e)===!1)return void(c.stop=!0)}function l(a){for(var b;a.next();)a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),l(b),b.detach()):a.remove()}function m(a){for(var b,c,e=d(a.range).createDocumentFragment();b=a.next();){if(a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),c=a.getSubtreeIterator(),b.appendChild(m(c)),c.detach()):a.remove(),10==b.nodeType)throw new M("HIERARCHY_REQUEST_ERR");e.appendChild(b)}return e}function n(a,b,c){var d,e=!(!b||!b.length),f=!!c;e&&(d=new RegExp("^("+b.join("|")+")$"));var g=[];return k(new p(a,!1),function(b){if((!e||d.test(b.nodeType))&&(!f||c(b))){var h=a.startContainer;if(b!=h||!N(h)||a.startOffset!=h.length){var i=a.endContainer;b==i&&N(i)&&0==a.endOffset||g.push(b)}}}),g}function o(a){var b="undefined"==typeof a.getName?"Range":a.getName();return"["+b+"("+J.inspectNode(a.startContainer)+":"+a.startOffset+", "+J.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function p(a,b){if(this.range=a,this.clonePartiallySelectedTextNodes=b,!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&N(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==c||N(this.sc)?T(this.sc,c,!0):this.sc.childNodes[this.so],this._last=this.ec!==c||N(this.ec)?T(this.ec,c,!0):this.ec.childNodes[this.eo-1])}}function q(a){return function(b,c){for(var d,e=c?b:b.parentNode;e;){if(d=e.nodeType,V(a,d))return e;e=e.parentNode}return null}}function r(a,b){if(ea(a,b))throw new M("INVALID_NODE_TYPE_ERR")}function s(a,b){if(!V(b,a.nodeType))throw new M("INVALID_NODE_TYPE_ERR")}function t(a,b){if(0>b||b>(N(a)?a.length:a.childNodes.length))throw new M("INDEX_SIZE_ERR")}function u(a,b){if(ca(a,!0)!==ca(b,!0))throw new M("WRONG_DOCUMENT_ERR")}function v(a){if(da(a,!0))throw new M("NO_MODIFICATION_ALLOWED_ERR")}function w(a,b){if(!a)throw new M(b)}function x(a,b){return b<=(N(a)?a.length:a.childNodes.length)}function y(a){return!!a.startContainer&&!!a.endContainer&&!(X&&(J.isBrokenNode(a.startContainer)||J.isBrokenNode(a.endContainer)))&&W(a.startContainer)==W(a.endContainer)&&x(a.startContainer,a.startOffset)&&x(a.endContainer,a.endOffset)}function z(a){if(!y(a))throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: ("+a.inspect()+")")}function A(a,b){z(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,g=c===e;N(e)&&f>0&&f0&&d=O(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function B(a){z(a);var b=a.commonAncestorContainer.parentNode.cloneNode(!1);return b.appendChild(a.cloneContents()),b.innerHTML}function C(a){a.START_TO_START=ka,a.START_TO_END=la,a.END_TO_END=ma,a.END_TO_START=na,a.NODE_BEFORE=oa,a.NODE_AFTER=pa,a.NODE_BEFORE_AND_AFTER=qa,a.NODE_INSIDE=ra}function D(a){C(a),C(a.prototype)}function E(a,b){return function(){z(this);var c,d,e=this.startContainer,f=this.startOffset,h=this.commonAncestorContainer,i=new p(this,!0);e!==h&&(c=T(e,h,!0),d=g(c),e=d.node,f=d.offset),k(i,v),i.reset();var j=a(i);return i.detach(),b(this,e,f,e,f),j}}function F(b,d){function e(a,b){return function(c){s(c,Z),s(W(c),$);var d=(a?f:g)(c);(b?h:i)(this,d.node,d.offset)}}function h(a,b,c){var e=a.endContainer,f=a.endOffset;(b!==a.startContainer||c!==a.startOffset)&&((W(b)!=W(e)||1==R(b,c,e,f))&&(e=b,f=c),d(a,b,c,e,f))}function i(a,b,c){var e=a.startContainer,f=a.startOffset;(b!==a.endContainer||c!==a.endOffset)&&((W(b)!=W(e)||-1==R(b,c,e,f))&&(e=b,f=c),d(a,e,f,b,c))}var j=function(){};j.prototype=a.rangePrototype,b.prototype=new j,K.extend(b.prototype,{setStart:function(a,b){r(a,!0),t(a,b),h(this,a,b)},setEnd:function(a,b){r(a,!0),t(a,b),i(this,a,b)},setStartAndEnd:function(){var a=arguments,b=a[0],c=a[1],e=b,f=c;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}d(this,b,c,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:e(!0,!0),setStartAfter:e(!1,!0),setEndBefore:e(!0,!1),setEndAfter:e(!1,!1),collapse:function(a){z(this),a?d(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):d(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){r(a,!0),d(this,a,0,a,U(a))},selectNode:function(a){r(a,!1),s(a,Z);var b=f(a),c=g(a);d(this,b.node,b.offset,c.node,c.offset)},extractContents:E(m,d),deleteContents:E(l,d),canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},splitBoundaries:function(){A(this)},splitBoundariesPreservingPositions:function(a){A(this,a)},normalizeBoundaries:function(){z(this);var a,b=this.startContainer,c=this.startOffset,e=this.endContainer,f=this.endOffset,g=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(e=a,f=a.length,a.appendData(b.data),Y(b))},h=function(a){var d=a.previousSibling;if(d&&d.nodeType==a.nodeType){b=a;var g=a.length;if(c=d.length,a.insertData(0,d.data),Y(d),b==e)f+=c,e=b;else if(e==a.parentNode){var h=O(a);f==h?(e=a,f=g):f>h&&f--}}},i=!0;if(N(e))f==e.length?g(e):0==f&&(a=e.previousSibling,a&&a.nodeType==e.nodeType&&(f=a.length,b==e&&(i=!1),a.appendData(e.data),Y(e),e=a));else{if(f>0){var j=e.childNodes[f-1];j&&N(j)&&g(j)}i=!this.collapsed}if(i){if(N(b))0==c?h(b):c==b.length&&(a=b.nextSibling,a&&a.nodeType==b.nodeType&&(e==a&&(e=b,f+=b.length),b.appendData(a.data),Y(a)));else if(cx",ga=3==fa.firstChild.nodeType}catch(ha){}a.features.htmlParsingConforms=ga;var ia=ga?function(a){var b=this.startContainer,c=Q(b);if(!b)throw new M("INVALID_STATE_ERR");var d=null;return 1==b.nodeType?d=b:N(b)&&(d=J.parentElement(b)),d=null===d||"HTML"==d.nodeName&&J.isHtmlNamespace(Q(d).documentElement)&&J.isHtmlNamespace(d)?c.createElement("body"):d.cloneNode(!1),d.innerHTML=a,J.fragmentFromNodeChildren(d)}:function(a){var b=d(this),c=b.createElement("body");return c.innerHTML=a,J.fragmentFromNodeChildren(c)},ja=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],ka=0,la=1,ma=2,na=3,oa=0,pa=1,qa=2,ra=3;K.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){z(this),u(this.startContainer,b.startContainer);var c,d,e,f,g=a==na||a==ka?"start":"end",h=a==la||a==ka?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],R(c,d,e,f)},insertNode:function(a){if(z(this),s(a,aa),v(this.startContainer),P(a,this.startContainer))throw new M("HIERARCHY_REQUEST_ERR");var b=h(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){z(this);var a,b;if(this.collapsed)return d(this).createDocumentFragment();if(this.startContainer===this.endContainer&&N(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=d(this).createDocumentFragment(),b.appendChild(a),b;var c=new p(this,!0);return a=j(c),c.detach(),a},canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},surroundContents:function(a){if(s(a,ba),!this.canSurroundContents())throw new M("INVALID_STATE_ERR");var b=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);h(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){z(this);for(var a,b=new I(d(this)),c=ja.length;c--;)a=ja[c],b[a]=this[a];return b},toString:function(){z(this);var a=this.startContainer;if(a===this.endContainer&&N(a))return 3==a.nodeType||4==a.nodeType?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new p(this,!0);return k(c,function(a){(3==a.nodeType||4==a.nodeType)&&b.push(a.data)}),c.detach(),b.join("")},compareNode:function(a){z(this);var b=a.parentNode,c=O(a);if(!b)throw new M("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return 0>d?e>0?qa:oa:e>0?pa:ra},comparePoint:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)<0?-1:R(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ia,toHtml:function(){return B(this)},intersectsNode:function(a,b){if(z(this),W(a)!=e(this))return!1;var c=a.parentNode,d=O(a);if(!c)return!0;var f=R(c,d,this.endContainer,this.endOffset),g=R(c,d+1,this.startContainer,this.startOffset);return b?0>=f&&g>=0:0>f&&g>0},isPointInRange:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)>=0&&R(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return i(this,a,!1)},intersectsOrTouchesRange:function(a){return i(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=R(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=R(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return-1==b&&d.setStart(a.startContainer,a.startOffset),1==c&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return-1==R(a.startContainer,a.startOffset,this.startContainer,this.startOffset)&&b.setStart(a.startContainer,a.startOffset),1==R(a.endContainer,a.endOffset,this.endContainer,this.endOffset)&&b.setEnd(a.endContainer,a.endOffset),b}throw new M("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==ra},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,U(a))<=0},containsRange:function(a){var b=this.intersection(a);return null!==b&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();return b.setEnd(d,d.length),this.containsRange(b)}return this.containsNodeContents(a)},getNodes:function(a,b){return z(this),n(this,a,b)},getDocument:function(){return d(this)},collapseBefore:function(a){this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var c=d(this),e=a.createRange(c);b=b||J.getBody(c),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);for(var d,e,f,g,h=[b],i=!1,j=!1;!j&&(d=h.pop());)if(3==d.nodeType)e=c+d.length,!i&&a.start>=c&&a.start<=e&&(this.setStart(d,a.start-c),i=!0),i&&a.end>=c&&a.end<=e&&(this.setEnd(d,a.end-c),j=!0),c=e;else for(g=d.childNodes,f=g.length;f--;)h.push(g[f])},getName:function(){return"DomRange"},equals:function(a){return I.rangesEqual(this,a)},isValid:function(){return y(this)},inspect:function(){return o(this)},detach:function(){}}),F(I,H),K.extend(I,{rangeProperties:ja,RangeIterator:p,copyComparisonConstants:D,createPrototypeRange:F,inspect:o,toHtml:B,getRangeDocument:d,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=I}),H.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;if(a.features.implementsDomRange&&!function(){function d(a){for(var b,c=m.length;c--;)b=m[c],a[b]=a.nativeRange[b];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function g(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);(f||g||h)&&(a.setEnd(d,e),a.setStart(b,c))}var k,l,m=h.rangeProperties;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,d(this)},h.createPrototypeRange(c,g),k=c.prototype,k.selectNode=function(a){this.nativeRange.selectNode(a),d(this)},k.cloneContents=function(){return this.nativeRange.cloneContents()},k.surroundContents=function(a){this.nativeRange.surroundContents(a),d(this)},k.collapse=function(a){this.nativeRange.collapse(a),d(this)},k.cloneRange=function(){return new c(this.nativeRange.cloneRange())},k.refresh=function(){d(this)},k.toString=function(){return this.nativeRange.toString()};var n=document.createTextNode("test");i(document).appendChild(n);var o=document.createRange();o.setStart(n,0),o.setEnd(n,0);try{o.setStart(n,1),k.setStart=function(a,b){this.nativeRange.setStart(a,b),d(this)},k.setEnd=function(a,b){this.nativeRange.setEnd(a,b),d(this)},l=function(a){return function(b){this.nativeRange[a](b),d(this)}}}catch(p){k.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}d(this)},k.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}d(this)},l=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(e){this.nativeRange[b](c),this.nativeRange[a](c)}d(this)}}}k.setStartBefore=l("setStartBefore","setEndBefore"),k.setStartAfter=l("setStartAfter","setEndAfter"),k.setEndBefore=l("setEndBefore","setStartBefore"),k.setEndAfter=l("setEndAfter","setStartAfter"),k.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},o.selectNodeContents(n),o.setEnd(n,3);var q=document.createRange();q.selectNodeContents(n),q.setEnd(n,4),q.setStart(n,2),-1==o.compareBoundaryPoints(o.START_TO_END,q)&&1==o.compareBoundaryPoints(o.END_TO_START,q)?k.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:k.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};var r=document.createElement("div");r.innerHTML="123";var s=r.firstChild,t=i(document);t.appendChild(r),o.setStart(s,1),o.setEnd(s,2),o.deleteContents(),"13"==s.data&&(k.deleteContents=function(){this.nativeRange.deleteContents(),d(this)},k.extractContents=function(){var a=this.nativeRange.extractContents();return d(this),a}),t.removeChild(r),t=null,f.isHostMethod(o,"createContextualFragment")&&(k.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),i(document).removeChild(n),k.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}(),a.features.implementsTextRange){var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return 0==a.compareEndPoints("StartToEnd",a)},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();if(e.isOrIsAncestorOf(b,i)||(i=b),!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");l.parentNode&&e.removeNode(l);for(var m,n,o,p,q,r=c?"StartToStart":"StartToEnd",s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;;){if(v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(r,a),0==m||s==u)break;if(-1==m){if(u==s+1)break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}if(q=l.nextSibling,-1==m&&q&&k(q)){h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(q.data)){var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;for(w=x.moveStart("character",y);-1==(m=x.compareEndPoints("StartToEnd",x));)w++,x.moveStart("character",1)}else w=h.text.length;p=new g(q,w)}else n=(d||!c)&&l.previousSibling,o=(d||c)&&l.nextSibling,p=o&&k(o)?new g(o,0):n&&k(n)?new g(n,n.data.length):new g(i,e.getNodeIndex(l));return e.removeNode(l),{boundaryPosition:p,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f,g,h=a.offset,j=e.getDocument(a.node),l=i(j).createTextRange(),m=k(a.node);return m?(c=a.node,d=c.parentNode):(g=a.node.childNodes,c=hb;++b)if(!C.isAncestorOf(a[0],a[b]))return!1;return!0}function m(a){var c=a.getNodes();if(!l(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}function n(a){return!!a&&"undefined"!=typeof a.text}function o(a,b){var c=new G(b);a._ranges=[c],h(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function p(b){if(b._ranges.length=0,"None"==b.docSelection.type)j(b);else{var c=b.docSelection.createRange();if(n(c))o(b,c);else{b.rangeCount=c.length;for(var d,e=L(c.item(0)),f=0;fh;++h)g.add(d.item(h));try{g.add(e)}catch(j){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}g.select(),p(a)}function r(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function s(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function t(a,b){for(var c,d,e=ba.length;e--;)if(c=ba[e],d=c.selection,"deleteAll"==b)s(d);else if(c.win==a)return"delete"==b?(ba.splice(e,1),!0):d;return"deleteAll"==b&&(ba.length=0),null}function u(a,c){for(var d,e=L(c[0].startContainer),f=M(e).createControlRange(),g=0,h=c.length;h>g;++g){d=m(c[g]);try{f.add(d)}catch(i){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}f.select(),p(a)}function v(a,b){if(a.win.document!=L(b))throw new H("WRONG_DOCUMENT_ERR")}function w(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function x(a){var b=[],c=new I(a.anchorNode,a.anchorOffset),d=new I(a.focusNode,a.focusOffset),e="function"==typeof a.getName?a.getName():"Selection";if("undefined"!=typeof a.rangeCount)for(var f=0,g=a.rangeCount;g>f;++f)b[f]=F.inspect(a.getRangeAt(f));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}a.config.checkSelectionRanges=!0;var y,z,A="boolean",B="number",C=a.dom,D=a.util,E=D.isHostMethod,F=a.DomRange,G=a.WrappedRange,H=a.DOMException,I=C.DomPosition,J=a.features,K="Control",L=C.getDocument,M=C.getBody,N=F.rangesEqual,O=E(window,"getSelection"),P=D.isHostObject(document,"selection");J.implementsWinGetSelection=O,J.implementsDocSelection=P;var Q=P&&(!O||a.config.preferTextRange);if(Q)y=f,a.isSelectionValid=function(a){var b=d(a,"isSelectionValid").document,c=b.selection;return"None"!=c.type||L(c.createRange().parentElement())==b};else{if(!O)return b.fail("Neither document.selection or window.getSelection() detected."),!1;y=e,a.isSelectionValid=function(){return!0}}a.getNativeSelection=y;var R=y();if(!R)return b.fail("Native selection was null (possibly issue 138?)"),!1;var S=a.createNativeRange(document),T=M(document),U=D.areHostProperties(R,["anchorNode","focusNode","anchorOffset","focusOffset"]);J.selectionHasAnchorAndFocus=U;var V=E(R,"extend");J.selectionHasExtend=V;var W=typeof R.rangeCount==B;J.selectionHasRangeCount=W;var X=!1,Y=!0,Z=V?function(b,c){var d=F.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.addRange(k(e)),b.extend(c.startContainer,c.startOffset)}:null;D.areHostMethods(R,["addRange","getRangeAt","removeAllRanges"])&&typeof R.rangeCount==B&&J.implementsDomRange&&!function(){var b=window.getSelection();if(b){for(var c=b.rangeCount,d=c>1,e=[],f=g(b),h=0;c>h;++h)e[h]=b.getRangeAt(h);var i=C.createTestElement(document,"",!1),j=i.appendChild(document.createTextNode(" ")),k=document.createRange();if(k.setStart(j,1),k.collapse(!0),b.removeAllRanges(),b.addRange(k),Y=1==b.rangeCount,b.removeAllRanges(),!d){var l=window.navigator.appVersion.match(/Chrome\/(.*?) /);if(l&&parseInt(l[1])>=36)X=!1;else{var m=k.cloneRange();k.setStart(j,0),m.setEnd(j,3),m.setStart(j,2),b.addRange(k),b.addRange(m),X=2==b.rangeCount}}for(C.removeNode(i),b.removeAllRanges(),h=0;c>h;++h)0==h&&f?Z?Z(b,e[h]):(a.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),b.addRange(e[h])):b.addRange(e[h])}}(),J.selectionSupportsMultipleRanges=X,J.collapsedNonEditableSelectionsSupported=Y;var $,_=!1;T&&E(T,"createControlRange")&&($=T.createControlRange(),D.areHostProperties($,["item","add"])&&(_=!0)),J.implementsControlRange=_,z=U?function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:!1};var aa;E(R,"getRangeAt")?aa=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:U&&(aa=function(b){var c=L(b.anchorNode),d=a.createRange(c);return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),r.prototype=a.selectionPrototype;var ba=[],ca=function(a){if(a&&a instanceof r)return a.refresh(),a;a=d(a,"getNativeSelection");var b=t(a),c=y(a),e=P?f(a):null;return b?(b.nativeSelection=c,b.docSelection=e,b.refresh()):(b=new r(c,e,a),ba.push({win:a,selection:b})),b};a.getSelection=ca,D.createAliasForDeprecatedMethod(a,"getIframeSelection","getSelection");var da=r.prototype;if(!Q&&U&&D.areHostMethods(R,["removeAllRanges","addRange"])){da.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),j(this)};var ea=function(a,b){Z(a.nativeSelection,b),a.refresh()};W?da.addRange=function(b,d){if(_&&P&&this.docSelection.type==K)q(this,b);else if(c(d)&&V)ea(this,b);else{var e;X?e=this.rangeCount:(this.removeAllRanges(),e=0);var f=k(b).cloneRange();try{this.nativeSelection.addRange(f)}catch(g){}if(this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==e+1){if(a.config.checkSelectionRanges){var i=aa(this.nativeSelection,this.rangeCount-1);i&&!N(i,b)&&(b=new G(i))}this._ranges[this.rangeCount-1]=b,h(this,b,ha(this.nativeSelection)),this.isCollapsed=z(this)}else this.refresh()}}:da.addRange=function(a,b){c(b)&&V?ea(this,a):(this.nativeSelection.addRange(k(a)),this.refresh())},da.setRanges=function(a){if(_&&P&&a.length>1)u(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;c>b;++b)this.addRange(a[b])}}}else{if(!(E(R,"empty")&&E(S,"select")&&_&&Q))return b.fail("No means of selecting a Range or TextRange was found"),!1;da.removeAllRanges=function(){try{if(this.docSelection.empty(),"None"!=this.docSelection.type){var a;if(this.anchorNode)a=L(this.anchorNode);else if(this.docSelection.type==K){var b=this.docSelection.createRange();b.length&&(a=L(b.item(0)))}if(a){var c=M(a).createTextRange();c.select(),this.docSelection.empty()}}}catch(d){}j(this)},da.addRange=function(b){this.docSelection.type==K?q(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,h(this,b,!1))},da.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?u(this,a):b&&this.addRange(a[0])}}da.getRangeAt=function(a){if(0>a||a>=this.rangeCount)throw new H("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()};var fa;if(Q)fa=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=M(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==K?p(b):n(c)?o(b,c):j(b)};else if(E(R,"getRangeAt")&&typeof R.rangeCount==B)fa=function(b){if(_&&P&&b.docSelection.type==K)p(b);else if(b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount,b.rangeCount){for(var c=0,d=b.rangeCount;d>c;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));h(b,b._ranges[b.rangeCount-1],ha(b.nativeSelection)),b.isCollapsed=z(b)}else j(b)};else{if(!U||typeof R.isCollapsed!=A||typeof S.collapsed!=A||!J.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;fa=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=aa(c,0),a._ranges=[b],a.rangeCount=1,i(a),a.isCollapsed=z(a)):j(a)}}da.refresh=function(a){var b=a?this._ranges.slice(0):null,c=this.anchorNode,d=this.anchorOffset;if(fa(this),a){var e=b.length;if(e!=this._ranges.length)return!0;if(this.anchorNode!=c||this.anchorOffset!=d)return!0;for(;e--;)if(!N(b[e],this._ranges[e]))return!0;return!1}};var ga=function(a,b){var c=a.getAllRanges();a.removeAllRanges();for(var d=0,e=c.length;e>d;++d)N(b,c[d])||a.addRange(c[d]);a.rangeCount||j(a)};_&&P?da.removeRange=function(a){if(this.docSelection.type==K){for(var b,c=this.docSelection.createRange(),d=m(a),e=L(c.item(0)),f=M(e).createControlRange(),g=!1,h=0,i=c.length;i>h;++h)b=c.item(h),b!==d||g?f.add(c.item(h)):g=!0;f.select(),p(this)}else ga(this,a)}:da.removeRange=function(a){ga(this,a)};var ha;!Q&&U&&J.implementsDomRange?(ha=g,da.isBackward=function(){return ha(this)}):ha=da.isBackward=function(){return!1},da.isBackwards=da.isBackward,da.toString=function(){for(var a=[],b=0,c=this.rangeCount;c>b;++b)a[b]=""+this._ranges[b];return a.join("")},da.collapse=function(b,c){v(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},da.collapseToStart=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},da.collapseToEnd=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},da.selectAllChildren=function(b){v(this,b);var c=a.createRange(b);c.selectNodeContents(b),this.setSingleRange(c)},da.deleteFromDocument=function(){if(_&&P&&this.docSelection.type==K){for(var a,b=this.docSelection.createRange();b.length;)a=b.item(0),b.remove(a),C.removeNode(a);this.refresh()}else if(this.rangeCount){var c=this.getAllRanges();if(c.length){this.removeAllRanges();for(var d=0,e=c.length;e>d;++d)c[d].deleteContents();this.addRange(c[e-1])}}},da.eachRange=function(a,b){for(var c=0,d=this._ranges.length;d>c;++c)if(a(this.getRangeAt(c)))return b},da.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},da.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},da.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b||[]))}),c},da.setStart=w(!0),da.setEnd=w(!1),a.rangePrototype.select=function(a){ca(this.getDocument()).setSingleRange(this,a)},da.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&1==b.length?this.addRange(b[0],"backward"):this.setRanges(b)},da.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)||!1},da.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},da.moveToBookmark=function(b){for(var c,d,e=[],f=0;c=b.rangeBookmarks[f++];)d=a.createRange(this.win),d.moveToBookmark(c),e.push(d);b.backward?this.setSingleRange(e[0],"backward"):this.setRanges(e)},da.saveRanges=function(){return{backward:this.isBackward(),ranges:this.callMethodOnEachRange("cloneRange")}},da.restoreRanges=function(a){this.removeAllRanges();for(var b,c=0;b=a.ranges[c];++c)this.addRange(b,a.backward&&0==c)},da.toHtml=function(){var a=[];return this.eachRange(function(b){a.push(F.toHtml(b))}),a.join("")},J.implementsTextRange&&(da.getNativeTextRange=function(){var c;if(c=this.docSelection){var d=c.createRange();if(n(d))return d;throw b.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return a.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw b.createError("getNativeTextRange: selection contains no range")}),da.getName=function(){return"WrappedSelection"},da.inspect=function(){return x(this)},da.detach=function(){t(this.win,"delete"),s(this)},r.detachAll=function(){t(null,"deleteAll")},r.inspect=x,r.isDirectionBackward=c,a.Selection=r,a.selectionPrototype=da,a.addShimListener(function(a){"undefined"==typeof a.getSelection&&(a.getSelection=function(){return ca(a)}),a=null})});var M=!1,N=function(a){M||(M=!0,!H.initialized&&H.config.autoInitialize&&l())};return F&&("complete"==document.readyState?N():(a(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",N,!1),J(window,"load",N))),H},this),function(a,b){"function"==typeof define&&define.amd?define(["./rangy-core"],a):"undefined"!=typeof module&&"object"==typeof exports?module.exports=a(require("rangy")):a(b.rangy)}(function(a){return a.createModule("SaveRestore",["WrappedRange"],function(a,b){function c(a,b){return(b||document).getElementById(a)}function d(a,b){var c,d="selectionBoundary_"+ +new Date+"_"+(""+Math.random()).slice(2),e=o.getDocument(a.startContainer),f=a.cloneRange();return f.collapse(b),c=e.createElement("span"),c.id=d,c.style.lineHeight="0",c.style.display="none",c.className="rangySelectionBoundary",c.appendChild(e.createTextNode(r)),f.insertNode(c),c}function e(a,d,e,f){var g=c(e,a);g?(d[f?"setStartBefore":"setEndBefore"](g),p(g)):b.warn("Marker element has been removed. Cannot restore selection.")}function f(a,b){return b.compareBoundaryPoints(a.START_TO_START,a)}function g(b,c){var e,f,g=a.DomRange.getRangeDocument(b),h=b.toString(),i=q(c);return b.collapsed?(f=d(b,!1),{document:g,markerId:f.id,collapsed:!0}):(f=d(b,!1),e=d(b,!0),{document:g,startMarkerId:e.id,endMarkerId:f.id,collapsed:!1,backward:i,toString:function(){return"original text: '"+h+"', new text: '"+b.toString()+"'"}})}function h(d,f){var g=d.document;"undefined"==typeof f&&(f=!0);var h=a.createRange(g);if(d.collapsed){var i=c(d.markerId,g);if(i){i.style.display="inline";var j=i.previousSibling;j&&3==j.nodeType?(p(i),h.collapseToPoint(j,j.length)):(h.collapseBefore(i),p(i))}else b.warn("Marker element has been removed. Cannot restore selection.")}else e(g,h,d.startMarkerId,!0),e(g,h,d.endMarkerId,!1);return f&&h.normalizeBoundaries(),h}function i(b,d){var e,h,i=[],j=q(d);b=b.slice(0),b.sort(f);for(var k=0,l=b.length;l>k;++k)i[k]=g(b[k],j);for(k=l-1;k>=0;--k)e=b[k],h=a.DomRange.getRangeDocument(e),e.collapsed?e.collapseAfter(c(i[k].markerId,h)):(e.setEndBefore(c(i[k].endMarkerId,h)),e.setStartAfter(c(i[k].startMarkerId,h)));return i}function j(c){if(!a.isSelectionValid(c))return b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null;var d=a.getSelection(c),e=d.getAllRanges(),f=1==e.length&&d.isBackward(),g=i(e,f);return f?d.setSingleRange(e[0],f):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}}function k(a){for(var b=[],c=a.length,d=c-1;d>=0;d--)b[d]=h(a[d],!0);return b}function l(b,c){if(!b.restored){var d=b.rangeInfos,e=a.getSelection(b.win),f=k(d),g=d.length;1==g&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0}}function m(a,b){var d=c(b,a);d&&p(d)}function n(a){for(var b,c=a.rangeInfos,d=0,e=c.length;e>d;++d)b=c[d],b.collapsed?m(a.doc,b.markerId):(m(a.doc,b.startMarkerId),m(a.doc,b.endMarkerId))}var o=a.dom,p=o.removeNode,q=a.Selection.isDirectionBackward,r="\ufeff";a.util.extend(a,{saveRange:g,restoreRange:h,saveRanges:i,restoreRanges:k,saveSelection:j,restoreSelection:l,removeMarkerElement:m,removeMarkers:n})}),a},this); diff --git a/vendor/assets/javascripts/textAngular-sanitize.min.js b/vendor/assets/javascripts/textAngular-sanitize.min.js index 75534aaa01..b3ebd34363 100644 --- a/vendor/assets/javascripts/textAngular-sanitize.min.js +++ b/vendor/assets/javascripts/textAngular-sanitize.min.js @@ -1 +1,6 @@ -!function(a,b){b["true"]=a,function(a,b){"use strict";function c(){this.$get=["$$sanitizeUri",function(a){return function(b){var c=[];return f(b,k(c,function(b,c){return!/^unsafe/.test(a(b,c))})),c.join("")}}]}function d(a){var c=[],d=k(c,b.noop);return d.chars(a),c.join("")}function e(a){var b,c={},d=a.split(",");for(b=0;b=0&&j[f]!=d;f--);if(f>=0){for(e=j.length-1;e>=f;e--)c.end&&c.end(j[e]);j.length=f}}var f,h,i,j=[],k=a;for(j.last=function(){return j[j.length-1]};a;){if(h=!0,j.last()&&C[j.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+j.last()+"[^>]*>","i"),function(a,b){return b=b.replace(r,"$1").replace(t,"$1"),c.chars&&c.chars(g(b)),""}),e("",j.last());else if(0===a.indexOf("",f)===f&&(c.comment&&c.comment(a.substring(4,f)),a=a.substring(f+3),h=!1)):s.test(a)?(i=a.match(s),i&&(a=a.replace(i[0],""),h=!1)):q.test(a)?(i=a.match(n),i&&(a=a.substring(i[0].length),i[0].replace(n,e),h=!1)):p.test(a)&&(i=a.match(m),i&&(a=a.substring(i[0].length),i[0].replace(m,d),h=!1)),h){f=a.indexOf("<");var u=0>f?a:a.substring(0,f);a=0>f?"":a.substring(f),c.chars&&c.chars(g(u))}if(a==k)throw l("badparse","The sanitizer was unable to parse the following block of html: {0}",a);k=a}e()}function g(a){if(!a)return"";var b=H.exec(a),c=b[1],d=b[3],e=b[2];return e&&(G.innerHTML=e.replace(/=b||173==b||b>=1536&&1540>=b||1807==b||6068==b||6069==b||b>=8204&&8207>=b||b>=8232&&8239>=b||b>=8288&&8303>=b||65279==b||b>=65520&&65535>=b?"&#"+b+";":a}).replace(//g,">")}function i(a){var c="",d=a.split(";");return b.forEach(d,function(a){var d=a.split(":");if(2==d.length){var e=I(b.lowercase(d[0])),a=I(b.lowercase(d[1]));("color"===e&&(a.match(/^rgb\([0-9%,\. ]*\)$/i)||a.match(/^rgba\([0-9%,\. ]*\)$/i)||a.match(/^hsl\([0-9%,\. ]*\)$/i)||a.match(/^hsla\([0-9%,\. ]*\)$/i)||a.match(/^#[0-9a-f]{3,6}$/i)||a.match(/^[a-z]*$/i))||"text-align"===e&&("left"===a||"right"===a||"center"===a||"justify"===a)||"float"===e&&("left"===a||"right"===a||"none"===a)||("width"===e||"height"===e)&&a.match(/[0-9\.]*(px|em|rem|%)/))&&(c+=e+": "+a+";")}}),c}function j(a,b,c,d){return"img"===a&&b["ta-insert-video"]&&("ta-insert-video"===c||"allowfullscreen"===c||"frameborder"===c||"contenteditble"===c&&"false"===d)?!0:!1}function k(a,c){var d=!1,e=b.bind(a,a.push);return{start:function(a,f,g){a=b.lowercase(a),!d&&C[a]&&(d=a),d||D[a]!==!0||(e("<"),e(a),b.forEach(f,function(d,g){var k=b.lowercase(g),l="img"===a&&"src"===k||"background"===k;("style"===k&&""!==(d=i(d))||j(a,f,k,d)||F[k]===!0&&(E[k]!==!0||c(d,l)))&&(e(" "),e(g),e('="'),e(h(d)),e('"'))}),e(g?"/>":">"))},end:function(a){a=b.lowercase(a),d||D[a]!==!0||(e("")),a==d&&(d=!1)},chars:function(a){d||e(h(a))}}}var l=b.$$minErr("$sanitize"),m=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,n=/^<\s*\/\s*([\w:-]+)[^>]*>/,o=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,p=/^/g,s=/]*?)>/i,t=//g,u=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,v=/([^\#-~| |!])/g,w=e("area,br,col,hr,img,wbr"),x=e("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=e("rp,rt"),z=b.extend({},y,x),A=b.extend({},x,e("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),B=b.extend({},y,e("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),C=e("script,style"),D=b.extend({},w,A,B,z),E=e("background,cite,href,longdesc,src,usemap"),F=b.extend({},E,e("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),G=document.createElement("pre"),H=/^(\s*)([\s\S]*?)(\s*)$/,I=function(){return String.prototype.trim?function(a){return b.isString(a)?a.trim():a}:function(a){return b.isString(a)?a.replace(/^\s\s*/,"").replace(/\s\s*$/,""):a}}();b.module("ngSanitize",[]).provider("$sanitize",c),b.module("ngSanitize").filter("linky",["$sanitize",function(a){var c=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,e=/^mailto:/;return function(f,g){function h(a){a&&n.push(d(a))}function i(a,c){n.push("'),h(c),n.push("")}if(!f)return f;for(var j,k,l,m=f,n=[];j=m.match(c);)k=j[0],j[2]==j[3]&&(k="mailto:"+k),l=j.index,h(m.substr(0,l)),i(k,j[0].replace(e,"")),m=m.substring(l+j[0].length);return h(m),a(n.join(""))}}])}(window,window.angular)}({},function(){return this}()); \ No newline at end of file +/** + * @license AngularJS v1.3.10 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +!function(a,b,c){"use strict";function d(){this.$get=["$$sanitizeUri",function(a){return function(b){"undefined"!=typeof arguments[1]&&(arguments[1].version="taSanitize");var c=[];return g(b,l(c,function(b,c){return!/^unsafe/.test(a(b,c))})),c.join("")}}]}function e(a){var c=[],d=l(c,b.noop);return d.chars(a),c.join("")}function f(a){var b,c={},d=a.split(",");for(b=0;b=0&&k[f]!=d;f--);if(f>=0){for(e=k.length-1;e>=f;e--)c.end&&c.end(k[e]);k.length=f}}"string"!=typeof a&&(a=null===a||"undefined"==typeof a?"":""+a);var f,g,i,j,k=[],l=a;for(k.last=function(){return k[k.length-1]};a;){if(j="",g=!0,k.last()&&G[k.last()])a=a.replace(new RegExp("([^]*)<\\s*\\/\\s*"+k.last()+"[^>]*>","i"),function(a,b){return b=b.replace(s,"$1").replace(v,"$1"),c.chars&&c.chars(h(b)),""}),e("",k.last());else{if(y.test(a)){if(i=a.match(y)){i[0];c.whitespace&&c.whitespace(i[0]),a=a.replace(i[0],""),g=!1}}else t.test(a)?(i=a.match(t),i&&(c.comment&&c.comment(i[1]),a=a.replace(i[0],""),g=!1)):u.test(a)?(i=a.match(u),i&&(a=a.replace(i[0],""),g=!1)):r.test(a)?(i=a.match(o),i&&(a=a.substring(i[0].length),i[0].replace(o,e),g=!1)):q.test(a)&&(i=a.match(n),i?(i[4]&&(a=a.substring(i[0].length),i[0].replace(n,d)),g=!1):(j+="<",a=a.substring(1)));g&&(f=a.indexOf("<"),j+=0>f?a:a.substring(0,f),a=0>f?"":a.substring(f),c.chars&&c.chars(h(j)))}if(a==l)throw m("badparse","The sanitizer was unable to parse the following block of html: {0}",a);l=a}e()}function h(a){if(!a)return"";var b=N.exec(a),c=b[1],d=b[3],e=b[2];return e&&(M.innerHTML=e.replace(/=b||173==b||b>=1536&&1540>=b||1807==b||6068==b||6069==b||b>=8204&&8207>=b||b>=8232&&8239>=b||b>=8288&&8303>=b||65279==b||b>=65520&&65535>=b?"&#"+b+";":a}).replace(//g,">")}function j(a){var c="",d=a.split(";");return b.forEach(d,function(a){var d=a.split(":");if(2==d.length){var e=O(b.lowercase(d[0])),a=O(b.lowercase(d[1]));(("color"===e||"background-color"===e)&&(a.match(/^rgb\([0-9%,\. ]*\)$/i)||a.match(/^rgba\([0-9%,\. ]*\)$/i)||a.match(/^hsl\([0-9%,\. ]*\)$/i)||a.match(/^hsla\([0-9%,\. ]*\)$/i)||a.match(/^#[0-9a-f]{3,6}$/i)||a.match(/^[a-z]*$/i))||"text-align"===e&&("left"===a||"right"===a||"center"===a||"justify"===a)||"text-decoration"===e&&("underline"===a||"line-through"===a)||"font-weight"===e&&"bold"===a||"float"===e&&("left"===a||"right"===a||"none"===a)||("width"===e||"height"===e)&&a.match(/[0-9\.]*(px|em|rem|%)/)||"direction"===e&&a.match(/^ltr|rtl|initial|inherit$/))&&(c+=e+": "+a+";")}}),c}function k(a,b,c,d){return"img"===a&&b["ta-insert-video"]&&("ta-insert-video"===c||"allowfullscreen"===c||"frameborder"===c||"contenteditable"===c&&"false"===d)?!0:!1}function l(a,c){var d=!1,e=b.bind(a,a.push);return{start:function(a,f,g){a=b.lowercase(a),!d&&G[a]&&(d=a),d||H[a]!==!0||(e("<"),e(a),b.forEach(f,function(d,g){var h=b.lowercase(g),l="img"===a&&"src"===h||"background"===h;("style"===h&&""!==(d=j(d))||k(a,f,h,d)||L[h]===!0&&(I[h]!==!0||c(d,l)))&&(e(" "),e(g),e('="'),e(i(d)),e('"'))}),e(g?"/>":">"))},comment:function(a){e(a)},whitespace:function(a){e(i(a))},end:function(a){a=b.lowercase(a),d||H[a]!==!0||(e("")),a==d&&(d=!1)},chars:function(a){d||e(i(a))}}}var m=b.$$minErr("$sanitize"),n=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,o=/^<\/\s*([\w:-]+)[^>]*>/,p=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,q=/^/g,t=/(^)/,u=/]*?)>/i,v=//g,w=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,x=/([^\#-~| |!])/g,y=/^(\s+)/,z=f("area,br,col,hr,img,wbr,input"),A=f("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),B=f("rp,rt"),C=b.extend({},B,A),D=b.extend({},A,f("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),E=b.extend({},B,f("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),F=f("animate,animateColor,animateMotion,animateTransform,circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set,stop,svg,switch,text,title,tspan,use"),G=f("script,style"),H=b.extend({},z,D,E,C,F),I=f("background,cite,href,longdesc,src,usemap,xlink:href"),J=f("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,id,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width"),K=f("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan"),L=b.extend({},I,K,J),M=document.createElement("pre"),N=/^(\s*)([\s\S]*?)(\s*)$/,O=function(){return String.prototype.trim?function(a){return b.isString(a)?a.trim():a}:function(a){return b.isString(a)?a.replace(/^\s\s*/,"").replace(/\s\s*$/,""):a}}();b.module("ngSanitize",[]).provider("$sanitize",d),b.module("ngSanitize").filter("linky",["$sanitize",function(a){var c=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,d=/^mailto:/;return function(f,g){function h(a){a&&n.push(e(a))}function i(a,c){n.push("'),h(c),n.push("")}if(!f)return f;for(var j,k,l,m=f,n=[];j=m.match(c);)k=j[0],j[2]||j[4]||(k=(j[3]?"http://":"mailto:")+k),l=j.index,h(m.substr(0,l)),i(k,j[0].replace(d,"")),m=m.substring(l+j[0].length);return h(m),a(n.join(""))}}])}(window,window.angular); diff --git a/vendor/assets/javascripts/textAngular.min.js b/vendor/assets/javascripts/textAngular.min.js index aba313ad9f..f293a823f5 100644 --- a/vendor/assets/javascripts/textAngular.min.js +++ b/vendor/assets/javascripts/textAngular.min.js @@ -1,2 +1,3 @@ -!function(a,b){b["true"]=a,angular.module("textAngularSetup",[]).value("taOptions",{toolbar:[["h1","h2","h3","h4","h5","h6","p","pre","quote"],["bold","italics","underline","ul","ol","redo","undo","clear"],["justifyLeft","justifyCenter","justifyRight","indent","outdent"],["html","insertImage","insertLink","insertVideo"]],classes:{focussed:"focussed",toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",disabled:"disabled",textEditor:"form-control",htmlEditor:"form-control"},setup:{textEditorSetup:function(){},htmlEditorSetup:function(){}},defaultFileDropHandler:function(a,b){var c=new FileReader;return"image"===a.type.substring(0,5)?(c.onload=function(){""!==c.result&&b("insertImage",c.result,!0)},c.readAsDataURL(a),!0):!1}}).value("taSelectableElements",["a","img"]).value("taCustomRenderers",[{selector:"img",customAttribute:"ta-insert-video",renderLogic:function(a){var b=angular.element(""),c=a.prop("attributes");angular.forEach(c,function(a){b.attr(a.name,a.value)}),b.attr("src",b.attr("ta-insert-video")),a.replaceWith(b)}}]).constant("taTranslations",{html:{buttontext:"Toggle HTML",tooltip:"Toggle html / Rich Text"},heading:{tooltip:"Heading "},p:{tooltip:"Paragraph"},pre:{tooltip:"Preformatted text"},ul:{tooltip:"Unordered List"},ol:{tooltip:"Ordered List"},quote:{tooltip:"Quote/unqoute selection or paragraph"},undo:{tooltip:"Undo"},redo:{tooltip:"Redo"},bold:{tooltip:"Bold"},italic:{tooltip:"Italic"},underline:{tooltip:"Underline"},justifyLeft:{tooltip:"Align text left"},justifyRight:{tooltip:"Align text right"},justifyCenter:{tooltip:"Center"},indent:{tooltip:"Increase indent"},outdent:{tooltip:"Decrease indent"},clear:{tooltip:"Clear formatting"},insertImage:{dialogPrompt:"Please enter an image URL to insert",tooltip:"Insert image",hotkey:"the - possibly language dependent hotkey ... for some future implementation"},insertVideo:{tooltip:"Insert video",dialogPrompt:"Please enter a youtube URL to embed"},insertLink:{tooltip:"Insert / edit link",dialogPrompt:"Please enter a URL to insert"}}).run(["taRegisterTool","$window","taTranslations","taSelection",function(a,b,c,d){a("html",{buttontext:c.html.buttontext,tooltiptext:c.html.tooltip,action:function(){this.$editor().switchView()},activeState:function(){return this.$editor().showHtml}});var e=function(a){return function(){return this.$editor().queryFormatBlockState(a)}},f=function(){return this.$editor().wrapSelection("formatBlock","<"+this.name.toUpperCase()+">")};angular.forEach(["h1","h2","h3","h4","h5","h6"],function(b){a(b.toLowerCase(),{buttontext:b.toUpperCase(),tooltiptext:c.heading.tooltip+b.charAt(1),action:f,activeState:e(b.toLowerCase())})}),a("p",{buttontext:"P",tooltiptext:c.p.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

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

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

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

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

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

    ":e>=11?"


    ":8>=e?"

     

    ":"

     

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

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

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

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

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

    ":e.ie>=11?"


    ":e.ie<=8?"

     

    ":"

     

    "):(B=void 0===e.ie||e.ie>=11?"<"+w.taDefaultWrap+">
    ":e.ie<=8?"<"+w.taDefaultWrap.toUpperCase()+">":"<"+w.taDefaultWrap+">",C=void 0===e.ie||e.ie>=11?"<"+w.taDefaultWrap+">
    ":e.ie<=8?"<"+w.taDefaultWrap.toUpperCase()+"> ":"<"+w.taDefaultWrap+"> "),E.$options||(E.$options={});var S=q(C),T=function(a){if(S(a))return a;var b=angular.element("
    "+a+"
    ");if(0===b.children().length)a="<"+w.taDefaultWrap+">"+a+"";else{var c,d=b[0].childNodes,e=!1;for(c=0;c";else if("#text"===h){var i=f.textContent;a+=i.trim()?"<"+w.taDefaultWrap+">"+i+"":i}else if(h.match(g))a+=f.outerHTML;else{var j=f.outerHTML||f.nodeValue;a+=""!==j.trim()?"<"+w.taDefaultWrap+">"+j+"":j}}else a="<"+w.taDefaultWrap+">"+a+""}return a};w.taPaste&&(A=s(w.taPaste)),v.addClass("ta-bind");var U;j["$undoManager"+(w.id||"")]=D.$undoManager={_stack:[],_index:0,_max:1e3,push:function(a){return"undefined"==typeof a||null===a||"undefined"!=typeof this.current()&&null!==this.current()&&a===this.current()?a:(this._indexthis._max&&this._stack.shift(),this._index=this._stack.length-1,a)},undo:function(){return this.setToIndex(this._index-1)},redo:function(){return this.setToIndex(this._index+1)},setToIndex:function(a){return 0>a||a>this._stack.length-1?void 0:(this._index=a,this.current())},current:function(){return this._stack[this._index]}};var V,W=j["$undoTaBind"+(w.id||"")]=function(){if(!H&&F){var a=D.$undoManager.undo();"undefined"!=typeof a&&null!==a&&(ka(a),Z(a,!1),V&&c.cancel(V),V=c(function(){v[0].focus(),m.setSelectionToElementEnd(v[0])},1))}},X=j["$redoTaBind"+(w.id||"")]=function(){if(!H&&F){var a=D.$undoManager.redo();"undefined"!=typeof a&&null!==a&&(ka(a),Z(a,!1),V&&c.cancel(V),V=c(function(){v[0].focus(),m.setSelectionToElementEnd(v[0])},1))}},Y=function(){if(F)return v[0].innerHTML;if(G)return v.val();throw"textAngular Error: attempting to update non-editable taBind"},Z=function(a,b,c){J=c||!1,("undefined"==typeof b||null===b)&&(b=F),("undefined"==typeof a||null===a)&&(a=Y()),S(a)?(""!==D.$viewValue&&D.$setViewValue(""),b&&""!==D.$undoManager.current()&&D.$undoManager.push("")):(ja(),D.$viewValue!==a&&(D.$setViewValue(a),b&&D.$undoManager.push(a))),D.$render()};j["updateTaBind"+(w.id||"")]=function(){H||Z(void 0,void 0,!0)};var $=function(a){return D.$oldViewValue=b(h(a),D.$oldViewValue,K)};if(v.attr("required")&&(D.$validators.required=function(a,b){return!S(a||b)}),D.$parsers.push($),D.$parsers.unshift(T),D.$formatters.push($),D.$formatters.unshift(T),D.$formatters.unshift(function(a){return D.$undoManager.push(a||"")}),G)if(j.events={},F){var _=!1,aa=function(a){var d=a.match(/content=["']*OneNote.File/i);if(a&&a.trim().length){if(a.match(/class=["']*Mso(Normal|List)/i)||a.match(/content=["']*Word.Document/i)||a.match(/content=["']*OneNote.File/i)){var e=a.match(/([\s\S]*?)/i);e=e?e[1]:a,e=e.replace(/[\s\S]*?<\/o:p>/gi,"").replace(/class=(["']|)MsoNormal(["']|)/gi,"");var f=angular.element("
    "+e+"
    "),g=angular.element("
    "),h={element:null,lastIndent:[],lastLi:null,isUl:!1};h.lastIndent.peek=function(){var a=this.length;return a>0?this[a-1]:void 0};for(var i=function(a){h.isUl=a,h.element=angular.element(a?"
      ":"
        "),h.lastIndent=[],h.lastIndent.peek=function(){var a=this.length;return a>0?this[a-1]:void 0},h.lastLevelMatch=null},k=0;k<=f[0].childNodes.length;k++)if(f[0].childNodes[k]&&"#text"!==f[0].childNodes[k].nodeName){var l=f[0].childNodes[k].tagName.toLowerCase();if("p"===l||"h1"===l||"h2"===l||"h3"===l||"h4"===l||"h5"===l||"h6"===l){var n=angular.element(f[0].childNodes[k]),o=(n.attr("class")||"").match(/MsoList(Bullet|Number|Paragraph)(CxSp(First|Middle|Last)|)/i);if(o){if(n[0].childNodes.length<2||n[0].childNodes[1].childNodes.length<1)continue;var p="bullet"===o[1].toLowerCase()||"number"!==o[1].toLowerCase()&&!(/^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]]":"
          "),h.lastLi.append(h.element);else if(null!=h.lastIndent.peek()&&h.lastIndent.peek()>r){for(;null!=h.lastIndent.peek()&&h.lastIndent.peek()>r;)if("li"!==h.element.parent()[0].tagName.toLowerCase()){if(!/[uo]l/i.test(h.element.parent()[0].tagName.toLowerCase()))break;h.element=h.element.parent(),h.lastIndent.pop()}else h.element=h.element.parent();h.isUl="ul"===h.element[0].tagName.toLowerCase(),p!==h.isUl&&(i(p),g.append(h.element))}h.lastLevelMatch=s,r!==h.lastIndent.peek()&&h.lastIndent.push(r),h.lastLi=angular.element("
        1. "),h.element.append(h.lastLi),h.lastLi.html(n.html().replace(/[\s\S]*?/gi,"")),n.remove()}else i(!1),g.append(n)}}var u=function(a){a=angular.element(a);for(var b=a[0].childNodes.length-1;b>=0;b--)a.after(a[0].childNodes[b]);a.remove()};angular.forEach(g.find("span"),function(a){a.removeAttribute("lang"),a.attributes.length<=0&&u(a)}),angular.forEach(g.find("font"),u),a=g.html(),d&&(a=g.html()||f.html())}else{if(a=a.replace(/<(|\/)meta[^>]*?>/gi,""),a.match(/<[^>]*?(ta-bind)[^>]*?>/)){if(a.match(/<[^>]*?(text-angular)[^>]*?>/)){var w=angular.element("
          "+a+"
          ");w.find("textarea").remove();for(var x=t.getByAttribute(w,"ta-bind"),y=0;y',"")}}else a.match(/^.<\/span>/gi)||(a=a.replace(/<(|\/)span[^>]*?>/gi,"")));a=a.replace(/
          ]*?>/gi,"").replace(/( | )<\/span>/gi," ")}//i.test(a)&&/(|).*/i.test(a)===!1&&(a=a.replace(/.*<\/li(\s.*)?>/i,"
            $&
          ")),a=a.replace(/^[ |\u00A0]+/gm,function(a){for(var b="",c=0;c").replace(/\t/g,"    "),A&&(a=A(j,{$html:a})||a),a=b(a,"",K),m.insertHtml(a,v[0]),c(function(){D.$setViewValue(Y()),_=!1,v.removeClass("processing-paste")},0)}else _=!1,v.removeClass("processing-paste")};v.on("paste",j.events.paste=function(b,e){if(e&&angular.extend(b,e),H||_)return b.stopPropagation(),b.preventDefault(),!1;_=!0,v.addClass("processing-paste");var f,g=(b.originalEvent||b).clipboardData;if(g&&g.getData&&g.types.length>0){for(var h="",i=0;i
    ');d.find("body").append(k),k[0].focus(),c(function(){a.restoreSelection(j),aa(k[0].innerHTML),v[0].focus(),k.remove()},0)}),v.on("cut",j.events.cut=function(a){H?a.preventDefault():c(function(){D.$setViewValue(Y())},0)}),v.on("keydown",j.events.keydown=function(a,b){b&&angular.extend(a,b),a.specialKey=y(a);var c;if(p.keyMappings.forEach(function(b){a.specialKey===b.commandKeyCode&&(a.specialKey=void 0),b.testForKey(a)&&(c=b.commandKeyCode),("UndoKey"===b.commandKeyCode||"RedoKey"===b.commandKeyCode)&&(b.enablePropagation||a.preventDefault())}),"undefined"!=typeof c&&(a.specialKey=c),"undefined"==typeof a.specialKey||"UndoKey"===a.specialKey&&"RedoKey"===a.specialKey||(a.preventDefault(),u.sendKeyCommand(j,a)),!H&&("UndoKey"===a.specialKey&&(W(),a.preventDefault()),"RedoKey"===a.specialKey&&(X(),a.preventDefault()),13===a.keyCode&&!a.shiftKey)){var d,e=function(a,b){for(var c=0;c$/i.test(f.innerHTML.trim())&&!f.nextSibling){d=angular.element(f);var k=d.parent();k.after(g),d.remove(),0===k.children().length&&k.remove(),m.setSelectionToElementStart(g[0]),a.preventDefault()}/^<[^>]+><\/[^>]+>$/i.test(f.innerHTML.trim())&&(d=angular.element(f),d.after(g),d.remove(),m.setSelectionToElementStart(g[0]),a.preventDefault())}}});var ba;if(v.on("keyup",j.events.keyup=function(a,b){if(b&&angular.extend(a,b),9===a.keyCode){var d=m.getSelection();return void(d.start.element===v[0]&&v.children().length&&m.setSelectionToElementStart(v.children()[0]))}if(U&&c.cancel(U),!H&&!L.test(a.keyCode)){if(""!==B&&13===a.keyCode&&!a.shiftKey){for(var e=m.getSelectionElement();!e.tagName.match(i)&&e!==v[0];)e=e.parentNode;if(e.tagName.toLowerCase()!==w.taDefaultWrap&&"li"!==e.tagName.toLowerCase()&&(""===e.innerHTML.trim()||"
    "===e.innerHTML.trim())){var f=angular.element(B);angular.element(e).replaceWith(f),m.setSelectionToElementStart(f[0])}}var g=Y();""!==B&&""===g.trim()?(ka(B),m.setSelectionToElementStart(v.children()[0])):"<"!==g.substring(0,1)&&""!==w.taDefaultWrap;var h=z!==a.keyCode&&M.test(a.keyCode);ba&&c.cancel(ba),ba=c(function(){Z(g,h,!0)},E.$options.debounce||400),h||(U=c(function(){D.$undoManager.push(g)},250)),z=a.keyCode}}),v.on("blur",j.events.blur=function(){I=!1,H?(J=!0,D.$render()):Z(void 0,void 0,!0)}),w.placeholder&&(e.ie>8||void 0===e.ie)){var ca;if(!w.id)throw"textAngular Error: An unique ID is required for placeholders to work";ca=k("#"+w.id+".placeholder-text:before",'content: "'+w.placeholder+'"'),j.$on("$destroy",function(){l(ca)})}v.on("focus",j.events.focus=function(){I=!0,v.removeClass("placeholder-text"),ja()}),v.on("mouseup",j.events.mouseup=function(){var a=m.getSelection();a.start.element===v[0]&&v.children().length&&m.setSelectionToElementStart(v.children()[0])}),v.on("mousedown",j.events.mousedown=function(a,b){b&&angular.extend(a,b),a.stopPropagation()})}else{v.on("change blur",j.events.change=j.events.blur=function(){H||D.$setViewValue(Y())}),v.on("keydown",j.events.keydown=function(a,b){if(b&&angular.extend(a,b),9===a.keyCode){var c=this.selectionStart,d=this.selectionEnd,e=v.val();if(a.shiftKey){var f=e.lastIndexOf("\n",c),g=e.lastIndexOf(" ",c);-1!==g&&g>=f&&(v.val(e.substring(0,g)+e.substring(g+1)),this.selectionStart=this.selectionEnd=c-1)}else v.val(e.substring(0,c)+" "+e.substring(d)),this.selectionStart=this.selectionEnd=c+1;a.preventDefault()}});var da=function(a,b){for(var c="",d=0;b>d;d++)c+=a;return c},ea=function(a,b,c){for(var d=0;d"):"#text"===e?void(c+=d.textContent):void(d.outerHTML&&(c+="ul"===e||"ol"===e?"\n"+fa(d,b):"\n"+da(" ",b)+d.outerHTML))}),c+="\n"+da(" ",b-1)+a.outerHTML.substring(a.outerHTML.lastIndexOf("<"))};D.$formatters.unshift(function(a){var b=angular.element("
    "+a+"
    ")[0].childNodes;return b.length>0&&(a="",ea(b,function(b,c){var d=c.nodeName.toLowerCase();return"#comment"===d?void(a+=""):"#text"===d?void(a+=c.textContent):void(c.outerHTML&&(a.length>0&&(a+="\n"),a+="ul"===d||"ol"===d?""+fa(c,0):""+c.outerHTML))})),a})}var ga,ha=function(a){return j.$emit("ta-element-select",this),a.preventDefault(),!1},ia=function(a,b){if(b&&angular.extend(a,b),!r&&!H){r=!0;var d;d=a.originalEvent?a.originalEvent.dataTransfer:a.dataTransfer,j.$emit("ta-drop-event",this,a,d),c(function(){r=!1,Z(void 0,void 0,!0)},100)}},ja=j["reApplyOnSelectorHandlers"+(w.id||"")]=function(){H||angular.forEach(n,function(a){v.find(a).off("click",ha).on("click",ha)})},ka=function(a){v[0].innerHTML=a},la=!1;D.$render=function(){if(!la){la=!0;var a=D.$viewValue||"";J||(F&&I&&(v.removeClass("placeholder-text"),ga&&c.cancel(ga),ga=c(function(){I||(v[0].focus(),m.setSelectionToElementEnd(v.children()[v.children().length-1])),ga=void 0},1)),F?(ka(w.placeholder?""===a?B:a:""===a?B:a),H?v.off("drop",ia):(ja(),v.on("drop",ia))):"textarea"!==v[0].tagName.toLowerCase()&&"input"!==v[0].tagName.toLowerCase()?ka(o(a)):v.val(a)),F&&w.placeholder&&(""===a?I?v.removeClass("placeholder-text"):v.addClass("placeholder-text"):v.removeClass("placeholder-text")),la=J=!1}},w.taReadonly&&(H=j.$eval(w.taReadonly),H?(v.addClass("ta-readonly"),("textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase())&&v.attr("disabled","disabled"),void 0!==v.attr("contenteditable")&&v.attr("contenteditable")&&v.removeAttr("contenteditable")):(v.removeClass("ta-readonly"),"textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase()?v.removeAttr("disabled"):F&&v.attr("contenteditable","true")),j.$watch(w.taReadonly,function(a,b){b!==a&&(a?(v.addClass("ta-readonly"),("textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase())&&v.attr("disabled","disabled"),void 0!==v.attr("contenteditable")&&v.attr("contenteditable")&&v.removeAttr("contenteditable"),angular.forEach(n,function(a){v.find(a).on("click",ha)}),v.off("drop",ia)):(v.removeClass("ta-readonly"),"textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase()?v.removeAttr("disabled"):F&&v.attr("contenteditable","true"),angular.forEach(n,function(a){v.find(a).off("click",ha)}),v.on("drop",ia)),H=a)})),F&&!H&&(angular.forEach(n,function(a){v.find(a).on("click",ha)}),v.on("drop",ia),v.on("blur",function(){e.webkit&&(f=!0)}))}}}]);var r=!1,s=angular.module("textAngular",["ngSanitize","textAngularSetup","textAngular.factories","textAngular.DOM","textAngular.validators","textAngular.taBind"]);return s.config([function(){angular.forEach(d,function(a,b){delete d[b]})}]),s.directive("textAngular",["$compile","$timeout","taOptions","taSelection","taExecCommand","textAngularManager","$document","$animate","$log","$q","$parse",function(b,c,d,e,f,g,h,i,j,k,l){return{require:"?ngModel",scope:{},restrict:"EA",priority:2,link:function(m,n,o,p){var q,r,s,t,u,v,w,x,y,z,A,B=o.serial?o.serial:Math.floor(1e16*Math.random());m._name=o.name?o.name:"textAngularEditor"+B;var C=function(a,b,d){c(function(){var c=function(){a.off(b,c),d.apply(this,arguments)};a.on(b,c)},100)};if(y=f(o.taDefaultWrap),angular.extend(m,angular.copy(d),{wrapSelection:function(a,b,c){"undo"===a.toLowerCase()?m["$undoTaBindtaTextElement"+B]():"redo"===a.toLowerCase()?m["$redoTaBindtaTextElement"+B]():(y(a,!1,b,m.defaultTagAttributes),c&&m["reApplyOnSelectorHandlerstaTextElement"+B](),m.displayElements.text[0].focus())},showHtml:m.$eval(o.taShowHtml)||!1}),o.taFocussedClass&&(m.classes.focussed=o.taFocussedClass),o.taTextEditorClass&&(m.classes.textEditor=o.taTextEditorClass),o.taHtmlEditorClass&&(m.classes.htmlEditor=o.taHtmlEditorClass),o.taDefaultTagAttributes)try{angular.extend(m.defaultTagAttributes,angular.fromJson(o.taDefaultTagAttributes))}catch(D){j.error(D)}o.taTextEditorSetup&&(m.setup.textEditorSetup=m.$parent.$eval(o.taTextEditorSetup)),o.taHtmlEditorSetup&&(m.setup.htmlEditorSetup=m.$parent.$eval(o.taHtmlEditorSetup)),o.taFileDrop?m.fileDropHandler=m.$parent.$eval(o.taFileDrop):m.fileDropHandler=m.defaultFileDropHandler,w=n[0].innerHTML,n[0].innerHTML="",m.displayElements={forminput:angular.element(""),html:angular.element(""),text:angular.element("
    "),scrollWindow:angular.element("
    "),popover:angular.element('
    '),popoverArrow:angular.element('
    '),popoverContainer:angular.element('
    '),resize:{overlay:angular.element('
    '),background:angular.element('
    '),anchors:[angular.element('
    '),angular.element('
    '),angular.element('
    '),angular.element('
    ')],info:angular.element('
    ')}},m.displayElements.popover.append(m.displayElements.popoverArrow),m.displayElements.popover.append(m.displayElements.popoverContainer),m.displayElements.scrollWindow.append(m.displayElements.popover),m.displayElements.popover.on("mousedown",function(a,b){return b&&angular.extend(a,b),a.preventDefault(),!1}),m.showPopover=function(a){m.displayElements.popover.css("display","block"),m.reflowPopover(a),i.addClass(m.displayElements.popover,"in"),C(h.find("body"),"click keyup",function(){m.hidePopover()})},m.reflowPopover=function(a){m.displayElements.text[0].offsetHeight-51>a[0].offsetTop?(m.displayElements.popover.css("top",a[0].offsetTop+a[0].offsetHeight+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("top").addClass("bottom")):(m.displayElements.popover.css("top",a[0].offsetTop-54+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("bottom").addClass("top"));var b=m.displayElements.text[0].offsetWidth-m.displayElements.popover[0].offsetWidth,c=a[0].offsetLeft+a[0].offsetWidth/2-m.displayElements.popover[0].offsetWidth/2;m.displayElements.popover.css("left",Math.max(0,Math.min(b,c))+"px"),m.displayElements.popoverArrow.css("margin-left",Math.min(c,Math.max(0,c-b))-11+"px")},m.hidePopover=function(){m.displayElements.popover.css("display",""),m.displayElements.popoverContainer.attr("style",""),m.displayElements.popoverContainer.attr("class","popover-content"),m.displayElements.popover.removeClass("in")},m.displayElements.resize.overlay.append(m.displayElements.resize.background),angular.forEach(m.displayElements.resize.anchors,function(a){m.displayElements.resize.overlay.append(a)}),m.displayElements.resize.overlay.append(m.displayElements.resize.info),m.displayElements.scrollWindow.append(m.displayElements.resize.overlay),m.reflowResizeOverlay=function(a){a=angular.element(a)[0],m.displayElements.resize.overlay.css({display:"block",left:a.offsetLeft-5+"px",top:a.offsetTop-5+"px",width:a.offsetWidth+10+"px",height:a.offsetHeight+10+"px"}),m.displayElements.resize.info.text(a.offsetWidth+" x "+a.offsetHeight)},m.showResizeOverlay=function(a){var b=h.find("body");z=function(c){var d={width:parseInt(a.attr("width")),height:parseInt(a.attr("height")),x:c.clientX,y:c.clientY};(void 0===d.width||isNaN(d.width))&&(d.width=a[0].offsetWidth),(void 0===d.height||isNaN(d.height))&&(d.height=a[0].offsetHeight),m.hidePopover();var e=d.height/d.width,f=function(b){function c(a){return Math.round(Math.max(0,a))}var f={x:Math.max(0,d.width+(b.clientX-d.x)),y:Math.max(0,d.height+(b.clientY-d.y))},g=void 0!==o.taResizeForceAspectRatio,h=o.taResizeMaintainAspectRatio,i=g||h&&!b.shiftKey;if(i){var j=f.y/f.x;f.x=e>j?f.x:f.y/e,f.y=e>j?f.x*e:f.y}var k=angular.element(a);k.css("height",c(f.y)+"px"),k.css("width",c(f.x)+"px"),m.reflowResizeOverlay(a)};b.on("mousemove",f),C(b,"mouseup",function(a){a.preventDefault(),a.stopPropagation(),b.off("mousemove",f),m.$apply(function(){m.hidePopover(),m.updateTaBindtaTextElement()},100)}),c.stopPropagation(),c.preventDefault()},m.displayElements.resize.anchors[3].off("mousedown"),m.displayElements.resize.anchors[3].on("mousedown",z),m.reflowResizeOverlay(a),C(b,"click",function(){m.hideResizeOverlay()})},m.hideResizeOverlay=function(){m.displayElements.resize.anchors[3].off("mousedown",z),m.displayElements.resize.overlay.css("display","")},m.setup.htmlEditorSetup(m.displayElements.html),m.setup.textEditorSetup(m.displayElements.text),m.displayElements.html.attr({id:"taHtmlElement"+B,"ng-show":"showHtml","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.text.attr({id:"taTextElement"+B,contentEditable:"true","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.scrollWindow.attr({"ng-hide":"showHtml"}),o.taDefaultWrap&&m.displayElements.text.attr("ta-default-wrap",o.taDefaultWrap),o.taUnsafeSanitizer&&(m.displayElements.text.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer),m.displayElements.html.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer)),m.displayElements.scrollWindow.append(m.displayElements.text),n.append(m.displayElements.scrollWindow),n.append(m.displayElements.html),m.displayElements.forminput.attr("name",m._name),n.append(m.displayElements.forminput),o.tabindex&&(n.removeAttr("tabindex"),m.displayElements.text.attr("tabindex",o.tabindex),m.displayElements.html.attr("tabindex",o.tabindex)),o.placeholder&&(m.displayElements.text.attr("placeholder",o.placeholder),m.displayElements.html.attr("placeholder",o.placeholder)),o.taDisabled&&(m.displayElements.text.attr("ta-readonly","disabled"),m.displayElements.html.attr("ta-readonly","disabled"),m.disabled=m.$parent.$eval(o.taDisabled),m.$parent.$watch(o.taDisabled,function(a){m.disabled=a,m.disabled?n.addClass(m.classes.disabled):n.removeClass(m.classes.disabled)})),o.taPaste&&(m._pasteHandler=function(a){return l(o.taPaste)(m.$parent,{$html:a})},m.displayElements.text.attr("ta-paste","_pasteHandler($html)")),b(m.displayElements.scrollWindow)(m),b(m.displayElements.html)(m),m.updateTaBindtaTextElement=m["updateTaBindtaTextElement"+B],m.updateTaBindtaHtmlElement=m["updateTaBindtaHtmlElement"+B],n.addClass("ta-root"),m.displayElements.scrollWindow.addClass("ta-text ta-editor "+m.classes.textEditor),m.displayElements.html.addClass("ta-html ta-editor "+m.classes.htmlEditor),m._actionRunning=!1;var E=!1;if(m.startAction=function(){return m._actionRunning=!0,E=a.saveSelection(),function(){E&&a.restoreSelection(E)}},m.endAction=function(){m._actionRunning=!1,E&&(m.showHtml?m.displayElements.html[0].focus():m.displayElements.text[0].focus(),a.removeMarkers(E)),E=!1,m.updateSelectedStyles(),m.showHtml||m["updateTaBindtaTextElement"+B]()},u=function(){m.focussed=!0,n.addClass(m.classes.focussed),x.focus(),n.triggerHandler("focus")},m.displayElements.html.on("focus",u),m.displayElements.text.on("focus",u),v=function(a){return m._actionRunning||h[0].activeElement===m.displayElements.html[0]||h[0].activeElement===m.displayElements.text[0]||(n.removeClass(m.classes.focussed),x.unfocus(),c(function(){m._bUpdateSelectedStyles=!1,n.triggerHandler("blur"),m.focussed=!1},0)),a.preventDefault(),!1},m.displayElements.html.on("blur",v),m.displayElements.text.on("blur",v),m.displayElements.text.on("paste",function(a){n.triggerHandler("paste",a)}),m.queryFormatBlockState=function(a){return!m.showHtml&&a.toLowerCase()===h[0].queryCommandValue("formatBlock").toLowerCase()},m.queryCommandState=function(a){return m.showHtml?"":h[0].queryCommandState(a)},m.switchView=function(){m.showHtml=!m.showHtml,i.enabled(!1,m.displayElements.html),i.enabled(!1,m.displayElements.text),m.showHtml?c(function(){return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.html[0].focus()},100):c(function(){return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.text[0].focus()},100)},o.ngModel){var F=!0;p.$render=function(){if(F){F=!1;var a=m.$parent.$eval(o.ngModel);void 0!==a&&null!==a||!w||""===w||p.$setViewValue(w)}m.displayElements.forminput.val(p.$viewValue),m.html=p.$viewValue||""},n.attr("required")&&(p.$validators.required=function(a,b){var c=a||b;return!(!c||""===c.trim())})}else m.displayElements.forminput.val(w),m.html=w;if(m.$watch("html",function(a,b){a!==b&&(o.ngModel&&p.$viewValue!==a&&p.$setViewValue(a),m.displayElements.forminput.val(a))}),o.taTargetToolbars)x=g.registerEditor(m._name,m,o.taTargetToolbars.split(","));else{var G=angular.element('
    ');o.taToolbar&&G.attr("ta-toolbar",o.taToolbar),o.taToolbarClass&&G.attr("ta-toolbar-class",o.taToolbarClass),o.taToolbarGroupClass&&G.attr("ta-toolbar-group-class",o.taToolbarGroupClass),o.taToolbarButtonClass&&G.attr("ta-toolbar-button-class",o.taToolbarButtonClass),o.taToolbarActiveButtonClass&&G.attr("ta-toolbar-active-button-class",o.taToolbarActiveButtonClass),o.taFocussedClass&&G.attr("ta-focussed-class",o.taFocussedClass),n.prepend(G),b(G)(m.$parent),x=g.registerEditor(m._name,m,["textAngularToolbar"+B])}m.$on("$destroy",function(){g.unregisterEditor(m._name),angular.element(window).off("blur")}),m.$on("ta-element-select",function(a,b){x.triggerElementSelect(a,b)&&m["reApplyOnSelectorHandlerstaTextElement"+B]()}),m.$on("ta-drop-event",function(a,b,d,e){m.displayElements.text[0].focus(),e&&e.files&&e.files.length>0?(angular.forEach(e.files,function(a){try{k.when(m.fileDropHandler(a,m.wrapSelection)||m.fileDropHandler!==m.defaultFileDropHandler&&k.when(m.defaultFileDropHandler(a,m.wrapSelection))).then(function(){m["updateTaBindtaTextElement"+B]()})}catch(b){j.error(b)}}),d.preventDefault(),d.stopPropagation()):c(function(){m["updateTaBindtaTextElement"+B]()},0)}),m._bUpdateSelectedStyles=!1,angular.element(window).on("blur",function(){m._bUpdateSelectedStyles=!1,m.focussed=!1}),m.updateSelectedStyles=function(){var a;A&&c.cancel(A),void 0!==(a=e.getSelectionElement())&&a.parentNode!==m.displayElements.text[0]?x.updateSelectedStyles(angular.element(a)):x.updateSelectedStyles(),m._bUpdateSelectedStyles&&(A=c(m.updateSelectedStyles,200))},q=function(){return m.focussed?void(m._bUpdateSelectedStyles||(m._bUpdateSelectedStyles=!0,m.$apply(function(){m.updateSelectedStyles()}))):void(m._bUpdateSelectedStyles=!1)},m.displayElements.html.on("keydown",q),m.displayElements.text.on("keydown",q),r=function(){m._bUpdateSelectedStyles=!1},m.displayElements.html.on("keyup",r),m.displayElements.text.on("keyup",r),s=function(a,b){b&&angular.extend(a,b),m.$apply(function(){return x.sendKeyCommand(a)?(m._bUpdateSelectedStyles||m.updateSelectedStyles(),a.preventDefault(),!1):void 0})},m.displayElements.html.on("keypress",s),m.displayElements.text.on("keypress",s),t=function(){m._bUpdateSelectedStyles=!1,m.$apply(function(){m.updateSelectedStyles()})},m.displayElements.html.on("mouseup",t),m.displayElements.text.on("mouseup",t)}}}]),s.service("textAngularManager",["taToolExecuteAction","taTools","taRegisterTool",function(a,b,c){var d={},e={};return{registerEditor:function(c,f,g){if(!c||""===c)throw"textAngular Error: An editor requires a name";if(!f)throw"textAngular Error: An editor requires a scope";if(e[c])throw'textAngular Error: An Editor with name "'+c+'" already exists';var h=[];return angular.forEach(g,function(a){d[a]&&h.push(d[a])}),e[c]={scope:f,toolbars:g,_registerToolbar:function(a){this.toolbars.indexOf(a.name)>=0&&h.push(a)},editorFunctions:{disable:function(){angular.forEach(h,function(a){a.disabled=!0})},enable:function(){angular.forEach(h,function(a){a.disabled=!1})},focus:function(){angular.forEach(h,function(a){a._parent=f,a.disabled=!1,a.focussed=!0,f.focussed=!0})},unfocus:function(){angular.forEach(h,function(a){a.disabled=!0,a.focussed=!1}),f.focussed=!1},updateSelectedStyles:function(a){angular.forEach(h,function(b){angular.forEach(b.tools,function(c){c.activeState&&(b._parent=f,c.active=c.activeState(a))})})},sendKeyCommand:function(c){var d=!1;return(c.ctrlKey||c.metaKey||c.specialKey)&&angular.forEach(b,function(b,e){if(b.commandKeyCode&&(b.commandKeyCode===c.which||b.commandKeyCode===c.specialKey))for(var g=0;g0)for(var k=0;k"),b&&b["class"]?d.addClass(b["class"]):d.addClass(g.classes.toolbarButton),d.attr("name",c.name),d.attr("ta-button","ta-button"),d.attr("ng-disabled","isDisabled()"),d.attr("tabindex","-1"),d.attr("ng-click","executeAction()"),d.attr("ng-class","displayActiveToolClass(active)"),b&&b.tooltiptext&&d.attr("title",b.tooltiptext),b&&!b.display&&!c._display&&(d[0].innerHTML="",b.buttontext&&(d[0].innerHTML=b.buttontext),b.iconclass)){var e=angular.element(""),f=d[0].innerHTML;e.addClass(b.iconclass),d[0].innerHTML="",d.append(e),f&&""!==f&&d.append(" "+f)}return c._lastToolDefinition=angular.copy(b),a(d)(c)};g.tools={},g._parent={disabled:!0,showHtml:!1,queryFormatBlockState:function(){return!1},queryCommandState:function(){return!1}};var k={$window:f,$editor:function(){return g._parent},isDisabled:function(){return"function"!=typeof this.$eval("disabled")&&this.$eval("disabled")||this.$eval("disabled()")||"html"!==this.name&&this.$editor().showHtml||this.$parent.disabled||this.$editor().disabled},displayActiveToolClass:function(a){return a?g.classes.toolbarButtonActive:""},executeAction:e};angular.forEach(g.toolbar,function(a){var b=angular.element("
    ");b.addClass(g.classes.toolbarGroup),angular.forEach(a,function(a){g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]),b.append(g.tools[a].$element)}),h.append(b)}),g.updateToolDisplay=function(a,b,c){var d=g.tools[a];if(d){if(d._lastToolDefinition&&!c&&(b=angular.extend({},d._lastToolDefinition,b)),null===b.buttontext&&null===b.iconclass&&null===b.display)throw'textAngular Error: Tool Definition for updating "'+a+'" does not have a valid display/iconclass/buttontext value';null===b.buttontext&&delete b.buttontext,null===b.iconclass&&delete b.iconclass,null===b.display&&delete b.display;var e=j(b,d);d.$element.replaceWith(e),d.$element=e}},g.addTool=function(a,b,c,e){g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]);var f;void 0===c&&(c=g.toolbar.length-1),f=angular.element(h.children()[c]),void 0===e?(f.append(g.tools[a].$element),g.toolbar[c][g.toolbar[c].length-1]=a):(f.children().eq(e).after(g.tools[a].$element),g.toolbar[c][e]=a)},b.registerToolbar(g),g.$on("$destroy",function(){b.unregisterToolbar(g.name)})}}}]),s.name});