From 83a111fdfb7c88c98751648ca7263301deec4474 Mon Sep 17 00:00:00 2001 From: Christopher Blanchard Date: Thu, 18 Jun 2020 18:26:45 +0200 Subject: [PATCH] feat(Admin): Add address validation to admin pages Adds address autocomplete to: - Customer page - Create new customer - Orders page - Create new order - Edit existing order --- lib/admin-customers.ts | 19 + lib/admin-orders.ts | 41 ++ lib/billing.ts | 2 +- lib/customer.ts | 6 + lib/index.ts | 17 +- ...pping-and-customer.ts => multishipping.ts} | 3 +- lib/shipping.ts | 14 +- package.json | 5 +- view/base/web/binding.js | 396 +----------------- 9 files changed, 95 insertions(+), 408 deletions(-) create mode 100644 lib/admin-customers.ts create mode 100644 lib/admin-orders.ts create mode 100644 lib/customer.ts rename lib/{multishipping-and-customer.ts => multishipping.ts} (92%) diff --git a/lib/admin-customers.ts b/lib/admin-customers.ts new file mode 100644 index 00000000..f2be830b --- /dev/null +++ b/lib/admin-customers.ts @@ -0,0 +1,19 @@ +import { setupBind } from "@ideal-postcodes/jsutil"; + +import { Config, setupAutocomplete } from "./extension"; + +import { selectors } from "./billing"; + +const parentScope = "fieldset"; +const parentTest = (e: HTMLElement) => e.className === "admin__fieldset"; + +const bind = (config: Config) => { + setupBind({ selectors, parentScope, parentTest }).forEach(({ targets }) => { + console.log(selectors); + setupAutocomplete(config, targets); + }); +}; + +const pageTest = () => window.location.pathname.includes("/customer"); + +export const bindings = { bind, pageTest }; diff --git a/lib/admin-orders.ts b/lib/admin-orders.ts new file mode 100644 index 00000000..a4de64ce --- /dev/null +++ b/lib/admin-orders.ts @@ -0,0 +1,41 @@ +import { setupBind } from "@ideal-postcodes/jsutil"; + +import { Config, setupAutocomplete } from "./extension"; + +export const billing = { + line_1: '[name="order[billing_address][street][0]"]', + line_2: '[name="order[billing_address][street][1]"]', + line_3: '[name="order[billing_address][street][2]"]', + postcode: '[name="order[billing_address][postcode]"]', + post_town: '[name="order[billing_address][city]"]', + organisation: '[name="order[billing_address][company]"]', + county: '[name="order[billing_address][region_id]"]', + country: '[name="order[billing_address][country_id]"]' +}; + +export const shipping = { + line_1: '[name="order[shipping_address][street][0]"]', + line_2: '[name="order[shipping_address][street][1]"]', + line_3: '[name="order[shipping_address][street][2]"]', + postcode: '[name="order[shipping_address][postcode]"]', + post_town: '[name="order[shipping_address][city]"]', + organisation: '[name="order[shipping_address][company]"]', + county: '[name="order[shipping_address][region_id]"]', + country: '[name="order[shipping_address][country_id]"]' +}; + +const selectorList = [billing, shipping]; + +const parentScope = "fieldset"; + +const bind = (config: Config) => { + selectorList.forEach(selectors => { + setupBind({ selectors, parentScope }).forEach(({ targets }) => { + setupAutocomplete(config, targets); + }); + }); +}; + +const pageTest = () => window.location.pathname.includes("/sales"); + +export const bindings = { bind, pageTest }; diff --git a/lib/billing.ts b/lib/billing.ts index 40c37523..b9d92d5b 100644 --- a/lib/billing.ts +++ b/lib/billing.ts @@ -30,6 +30,6 @@ const bind = (config: Config) => { }); }; -const pageTest = () => true; +const pageTest = () => window.location.pathname.includes("/checkout"); export const bindings = { bind, pageTest }; diff --git a/lib/customer.ts b/lib/customer.ts new file mode 100644 index 00000000..6f609cf7 --- /dev/null +++ b/lib/customer.ts @@ -0,0 +1,6 @@ +import { bindings as b } from "./multishipping"; +const { bind } = b; + +const pageTest = () => window.location.pathname.includes("/customer/address"); + +export const bindings = { bind, pageTest }; diff --git a/lib/index.ts b/lib/index.ts index fa622db7..3644e04a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -2,6 +2,19 @@ import { setup } from "@ideal-postcodes/jsutil"; import { bindings as shipping } from "./shipping"; import { bindings as billing } from "./billing"; -import { bindings as multiAndCustomer } from "./multishipping-and-customer"; +import { bindings as customer } from "./customer"; +import { bindings as multishipping } from "./multishipping"; +import { bindings as adminOrders } from "./admin-orders"; +import { bindings as adminCustomers } from "./admin-customers"; -setup({ bindings: [shipping, billing, multiAndCustomer], window }); +setup({ + bindings: [ + shipping, + billing, + customer, + multishipping, + adminOrders, + adminCustomers + ], + window +}); diff --git a/lib/multishipping-and-customer.ts b/lib/multishipping.ts similarity index 92% rename from lib/multishipping-and-customer.ts rename to lib/multishipping.ts index 7820a9eb..73bcf4e5 100644 --- a/lib/multishipping-and-customer.ts +++ b/lib/multishipping.ts @@ -25,7 +25,6 @@ const linesIdentifier = { }; const bind = (config: Config) => { - console.log("Calling bind"); setupBind({ selectors, parentTest: e => e.getAttribute("id") === "form-validate" @@ -36,6 +35,6 @@ const bind = (config: Config) => { }); }; -const pageTest = () => true; +const pageTest = () => window.location.pathname.includes("/multishipping"); export const bindings = { bind, pageTest }; diff --git a/lib/shipping.ts b/lib/shipping.ts index 2175f9d0..4993ed55 100644 --- a/lib/shipping.ts +++ b/lib/shipping.ts @@ -1,4 +1,5 @@ import { setupBind, Binding } from "@ideal-postcodes/jsutil"; +import { selectors } from "./billing"; import { Config, @@ -7,17 +8,6 @@ import { hoistCountry } from "./extension"; -const selectors = { - line_1: '[name="street[0]"]', - line_2: '[name="street[1]"]', - line_3: '[name="street[2]"]', - postcode: '[name="postcode"]', - post_town: '[name="city"]', - organisation: '[name="company"]', - county: '[name="region"]', - country: '[name="country_id"]' -}; - const bind = (config: Config) => { setupBind({ selectors }).forEach(({ targets }) => { hoistCountry(config, targets); @@ -26,6 +16,6 @@ const bind = (config: Config) => { }); }; -const pageTest = () => true; +const pageTest = () => window.location.pathname.includes("/checkout"); export const bindings: Binding = { bind, pageTest }; diff --git a/package.json b/package.json index 1a0741df..3713b8d6 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ }, "scripts": { "semantic-release": "semantic-release", - "start": "rollup -c", + "start": "make bootstrap && make set-base-url", + "build": "rollup -c", "watch": "rollup -cw", "lint": "eslint lib/**/*.ts", "test:e2e": "cypress run --project test/e2e", - "test:all": "make bootstrap && make set-base-url && npm run test:e2e && make down", + "test:all": "npm start && npm run test:e2e && make down", "test:snapshot": "cypress run --project test/snapshot", "test:watch": "rollup -c test/snapshot/rollup.config.js -w", "test": "npm run test:snapshot" diff --git a/view/base/web/binding.js b/view/base/web/binding.js index 5266d9d6..fa972b36 100644 --- a/view/base/web/binding.js +++ b/view/base/web/binding.js @@ -1,389 +1,7 @@ -(function() { - var toBoolean = function(arg, def) { - if (arg === undefined) return def; - return arg; - }; - - /** - * IdpcBinding - * - * Binds Ideal Postcodes address search functionality to magento frontend components - */ - var IdpcBinding = function(options) { - this.$ = options.jQuery || options.$; - this.api_key = options.api_key; - - // Exit if key not present - if (typeof this.api_key !== "string") return; - if (this.api_key.trim().length === 0) return; - - this.form = options.container; - - // Should postcode lookup be enabled - this.postcodeLookup = toBoolean(options.postcodeLookup, true); - // Should address autocomplete be enabled - this.addressAutocomplete = toBoolean(options.addressAutocomplete, true); - // Should address search update organisation field - this.populateOrganisation = toBoolean(options.populateOrganisation, true); - // Insert county as region. Deactiveated by default - county data should be discarded if possible - this.requireCounty = toBoolean(options.requireCounty, false); - // Should country field be hoisted up as first address field - this.hoistCountryField = toBoolean(options.hoistCountryField, true); - - this.lineOneIdentifier = options.lineOneIdentifier || '[name="street[0]"]'; - this.lineTwoIdentifier = options.lineTwoIdentifier || '[name="street[1]"]'; - this.lineThreeIdentifier = - options.lineThreeIdentifier || '[name="street[2]"]'; - this.postcodeIdentifier = options.postcodeIdentifier || '[name="postcode"]'; - this.postTownIdentifier = options.postTownIdentifier || '[name="city"]'; - this.organisationIdentifier = - options.organisationIdentifier || '[name="company"]'; - this.countyIdentifier = options.countyIdentifier || '[name="region"]'; - this.countryIdentifier = options.countryIdentifier || '[name="country_id"]'; - this.addressLinesIdentifier = - options.addressLinesIdentifier || "fieldset.field"; - - this._interval = null; - this._autocompleteInstances = []; - this._postcodeLookupInstances = []; - - this.load(); - this.watch(); - }; - - /** - * Returns a jQuery instance of form - */ - IdpcBinding.prototype.$form = function() { - return this.$(this.form); - }; - - IdpcBinding.prototype.$lineOne = function() { - return this.$form().find(this.lineOneIdentifier); - }; - - IdpcBinding.prototype.$lineTwo = function() { - return this.$form().find(this.lineTwoIdentifier); - }; - - IdpcBinding.prototype.$lineThree = function() { - return this.$form().find(this.lineThreeIdentifier); - }; - - /** - * Returns number of address lines - */ - IdpcBinding.prototype.nLines = function() { - if (this.$lineThree().length) return 3; - if (this.$lineTwo().length) return 2; - if (this.$lineOne().length) return 1; - // System default as fallback - return 2; - }; - - IdpcBinding.prototype.$organisation = function() { - return this.$form().find(this.organisationIdentifier); - }; - - IdpcBinding.prototype.$postTown = function() { - return this.$form().find(this.postTownIdentifier); - }; - - IdpcBinding.prototype.$postcode = function() { - return this.$form().find(this.postcodeIdentifier); - }; - - IdpcBinding.prototype.$county = function() { - return this.$form().find(this.countyIdentifier); - }; - - IdpcBinding.prototype.$country = function() { - return this.$form().find(this.countryIdentifier); - }; - - // Set country field according to full country name `country` - IdpcBinding.prototype.setCountry = function(address) { - var country; - if (address.country === "Channel Islands") { - country = address.post_town; - } else { - country = address.country; - } - - var $country = this.$country(); - if ($country.length === 0) return; - - if ($country.is("select")) { - // Convert to ISO code if select field - this.$country().val(IdpcBinding.toIso[country.toLowerCase()]); - // Manually trigger change event to propagate any changes magento needs to do - $country.trigger("change"); - } - if ($country.is("input")) this.$country().val(country); - }; - - // Hoists country field up before address lines - IdpcBinding.prototype.hoistCountry = function() { - if (this.hoistCountryField !== true) return; - - $countryContainer = this.$country().closest("div.field"); - $linesContainer = this.$linesContainer(); - if ($countryContainer.length === 0) return; - if ($linesContainer.length === 0) return; - $countryContainer.insertBefore($linesContainer); - }; - - /** - * Returns the containing div of address lines - */ - IdpcBinding.prototype.$linesContainer = function() { - return this.$lineOne().closest(this.addressLinesIdentifier); - }; - - // Apply address search functionality within form - IdpcBinding.prototype.load = function() { - // Guard: Exit if instantiated - if (this.loaded()) return; - // Guard: Check if line 1 address field is present - if (this.addressFieldsPresent() !== true) return; - - // Eagerly mark as loaded to prevent loop upon error - this.loaded(true); - - this.hoistCountry(); - this.applyAutocomplete(); - this.applyPostcodeLookup(); - this.observeCountry(); - }; - - // Listens for change in country selection. Activates plugins where appropriate - IdpcBinding.prototype.observeCountry = function() { - var self = this; - this.$country().change(function() { - self.detectCountry(); - }); - }; - - // Check if address fields are present by looking for line one - IdpcBinding.prototype.addressFieldsPresent = function() { - return this.$lineOne().length !== 0; - }; - - // Determines whether address search has been applied within container - // - If boolean argument provided, marks search as applied - IdpcBinding.prototype.loaded = function(attrValue) { - var $form = this.$form(); - - if (attrValue === true) { - $form.attr("idpc", "true"); - return attrValue; - } - - return $form.attr("idpc"); - }; - - // Append line to last elem in lines - var append = function(lines, line) { - var n = lines.length - 1; - if (!line) return; - lines[n] = lines[n] + ", " + line.trim(); - }; - - IdpcBinding.prototype.handleAddressSelection = function(address) { - // Reduce lines to number of applicable lines in form - var n = this.nLines(); - var lines = [address.line_1, address.line_2, address.line_3].reduce( - function(lines, line, i) { - if (i < n) lines.push(line); - if (i >= n) append(lines, line); - return lines; - }, - [] - ); - this.updateField(this.$lineOne, lines[0] || ""); - this.updateField(this.$lineTwo, lines[1] || ""); - this.updateField(this.$lineThree, lines[2] || ""); - this.updateField(this.$postTown, address.post_town); - this.updateField(this.$postcode, address.postcode); - - this.setCountry(address); - if (this.requireCounty === true) { - this.updateField(this.$county, address.county); - } - if (this.populateOrganisation === true) { - this.updateField(this.$organisation, address.organisation_name); - } - }; - - // Update field and also trigger a change event - // Otherwise magento won't know the field has been updated - IdpcBinding.prototype.updateField = function(field, value) { - var $field = field.call(this); - $field.val(value); - $field.trigger("change"); - }; - - IdpcBinding.prototype.handleError = function(error) {}; - - IdpcBinding.prototype.applyAutocomplete = function() { - if (this.addressAutocomplete !== true) return; - - var self = this; - - controller = new IdealPostcodes.Autocomplete.Controller({ - api_key: self.api_key, - checkKey: true, - onLoaded: function() { - self._autocompleteInstances.push(controller); - self.detectCountry(); // Check if correct country is engaged - }, - inputField: "#" + self.$lineOne().attr("id"), - onAddressRetrieved: self.handleAddressSelection.bind(self), - onSearchError: self.handleError.bind(self) - }); - }; - - IdpcBinding.prototype.applyPostcodeLookup = function() { - var self = this; - var $ = this.$; - - if (this.postcodeLookup !== true) return; - - var $linesContainer = this.$linesContainer(); - if ($linesContainer.length === 0) return; - - // Instantiate lookup container and insert to DOM - var lookupContainer = $('
').insertBefore( - $linesContainer - ); - - // Insert postode lookup - var $instance = lookupContainer.setupPostcodeLookup({ - api_key: self.api_key, - check_key: true, - onLoaded: function() { - // Add search label - $instance.prepend( - $( - '' - ) - ); - self._postcodeLookupInstances.push($instance); - self.detectCountry(); // Check if correct country is engaged - }, - onAddressSelected: self.handleAddressSelection.bind(self), - onSearchError: self.handleError.bind(self) - }); - }; - - /** - * Bind Ideal Postcodes to address fields that appear now (or maybe later) in a specific form - */ - IdpcBinding.prototype.watch = function(options) { - var self = this; - this._interval = setInterval(function() { - self.load(); - }, 1000); - }; - - /** - * Hide all postcode lookup and autocomplete functionality - */ - IdpcBinding.prototype.hideAll = function() { - this._autocompleteInstances.forEach(function(instance) { - var interface = instance.interface; - if (!interface) return; - var input = interface.input; - - // Disable current autocomplet activity just in case - interface._onBlurBound(); - - // Detact listeners - input.removeEventListener("input", interface._onInputBound); - input.removeEventListener("blur", interface._onBlurBound); - input.removeEventListener("focus", interface._onFocusBound); - input.removeEventListener("keydown", interface._onKeyDownBound); - interface.suggestionList.removeEventListener( - "mousedown", - interface._onMousedownBound - ); - }); - this._postcodeLookupInstances.forEach(function($instance) { - $instance.hide(); - }); - }; - - var NOOP = function() {}; - - IdpcBinding.prototype.showAll = function() { - this._autocompleteInstances.forEach(function(instance) { - var interface = instance.interface; - if (interface) interface.initialiseEventListeners(); - }); - this._postcodeLookupInstances.forEach(function($instance) { - $instance.show(); - }); - }; - - // Returns true if current country is supported - IdpcBinding.prototype.countrySupported = function() { - var currentCountry = IdpcBinding.toName[this.$country().val()]; - return currentCountry !== undefined; - }; - - IdpcBinding.prototype.detectCountry = function() { - if (this.countrySupported()) { - return this.showAll(); - } else { - return this.hideAll(); - } - }; - - // Mapping of ISO country codes to name - IdpcBinding.toName = { - GB: "United Kingdom", - JE: "Jersey", - IM: "Isle of Man", - GG: "Guernsey" - }; - - // Mapping of countries to ISO country codes - IdpcBinding.toIso = { - "united kingdom": "GB", - england: "GB", - scotland: "GB", - wales: "GB", - "northern ireland": "GB", - jersey: "JE", - "isle of man": "IM", - guernsey: "GG" - }; - - // Address update on account - var IdpcWatcher = function(options) { - var self = this; - this.$forms = []; - this.bindings = []; - var attach = function() { - jQuery(options.targets).each(function(_, form) { - var $form = jQuery(form); - if (self.isCached($form)) return; - self.$forms.push($form); - var o = jQuery.extend({ container: $form }, options.options); - self.bindings.push(new IdpcBinding(o)); - }); - }; - attach(); - this.interval = setInterval(attach, 3000); - }; - - IdpcWatcher.prototype.isCached = function($form) { - for (var i = 0; i < this.$forms.length; i += 1) { - if (this.$forms[i][0] === $form[0]) return true; - } - return false; - }; - - window.IdpcBinding = IdpcBinding; - window.IdpcWatcher = IdpcWatcher; -})(window); +/** + * @license + * Ideal Postcodes + * Magento Integration + * Copyright IDDQD Limited, all rights reserved + */ +!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(t,n,e){return t(e={path:n,exports:{},require:function(t,n){return function(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}(null==n&&e.path)}},e.exports),e.exports}var e=function(t){return t&&t.Math==Math&&t},r=e("object"==typeof globalThis&&globalThis)||e("object"==typeof window&&window)||e("object"==typeof self&&self)||e("object"==typeof t&&t)||Function("return this")(),o=function(t){try{return!!t()}catch(t){return!0}},i=!o((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),u={}.propertyIsEnumerable,c=Object.getOwnPropertyDescriptor,a={f:c&&!u.call({1:2},1)?function(t){var n=c(this,t);return!!n&&n.enumerable}:u},f=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}},s={}.toString,l=function(t){return s.call(t).slice(8,-1)},p="".split,d=o((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==l(t)?p.call(t,""):Object(t)}:Object,y=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},v=function(t){return d(y(t))},g=function(t){return"object"==typeof t?null!==t:"function"==typeof t},h=function(t,n){if(!g(t))return t;var e,r;if(n&&"function"==typeof(e=t.toString)&&!g(r=e.call(t)))return r;if("function"==typeof(e=t.valueOf)&&!g(r=e.call(t)))return r;if(!n&&"function"==typeof(e=t.toString)&&!g(r=e.call(t)))return r;throw TypeError("Can't convert object to primitive value")},m={}.hasOwnProperty,b=function(t,n){return m.call(t,n)},w=r.document,S=g(w)&&g(w.createElement),O=function(t){return S?w.createElement(t):{}},_=!i&&!o((function(){return 7!=Object.defineProperty(O("div"),"a",{get:function(){return 7}}).a})),j=Object.getOwnPropertyDescriptor,E={f:i?j:function(t,n){if(t=v(t),n=h(n,!0),_)try{return j(t,n)}catch(t){}if(b(t,n))return f(!a.f.call(t,n),t[n])}},T=function(t){if(!g(t))throw TypeError(String(t)+" is not an object");return t},L=Object.defineProperty,P={f:i?L:function(t,n,e){if(T(t),n=h(n,!0),T(e),_)try{return L(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported");return"value"in e&&(t[n]=e.value),t}},A=i?function(t,n,e){return P.f(t,n,f(1,e))}:function(t,n,e){return t[n]=e,t},k=function(t,n){try{A(r,t,n)}catch(e){r[t]=n}return n},C=r["__core-js_shared__"]||k("__core-js_shared__",{}),M=Function.toString;"function"!=typeof C.inspectSource&&(C.inspectSource=function(t){return M.call(t)});var I,N,x,G=C.inspectSource,D=r.WeakMap,F="function"==typeof D&&/native code/.test(G(D)),B=n((function(t){(t.exports=function(t,n){return C[t]||(C[t]=void 0!==n?n:{})})("versions",[]).push({version:"3.6.5",mode:"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),R=0,q=Math.random(),V=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++R+q).toString(36)},H=B("keys"),K=function(t){return H[t]||(H[t]=V(t))},W={},J=r.WeakMap;if(F){var Q=new J,z=Q.get,U=Q.has,X=Q.set;I=function(t,n){return X.call(Q,t,n),n},N=function(t){return z.call(Q,t)||{}},x=function(t){return U.call(Q,t)}}else{var Z=K("state");W[Z]=!0,I=function(t,n){return A(t,Z,n),n},N=function(t){return b(t,Z)?t[Z]:{}},x=function(t){return b(t,Z)}}var Y,$={set:I,get:N,has:x,enforce:function(t){return x(t)?N(t):I(t,{})},getterFor:function(t){return function(n){var e;if(!g(n)||(e=N(n)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return e}}},tt=n((function(t){var n=$.get,e=$.enforce,o=String(String).split("String");(t.exports=function(t,n,i,u){var c=!!u&&!!u.unsafe,a=!!u&&!!u.enumerable,f=!!u&&!!u.noTargetGet;"function"==typeof i&&("string"!=typeof n||b(i,"name")||A(i,"name",n),e(i).source=o.join("string"==typeof n?n:"")),t!==r?(c?!f&&t[n]&&(a=!0):delete t[n],a?t[n]=i:A(t,n,i)):a?t[n]=i:k(n,i)})(Function.prototype,"toString",(function(){return"function"==typeof this&&n(this).source||G(this)}))})),nt=r,et=function(t){return"function"==typeof t?t:void 0},rt=function(t,n){return arguments.length<2?et(nt[t])||et(r[t]):nt[t]&&nt[t][n]||r[t]&&r[t][n]},ot=Math.ceil,it=Math.floor,ut=function(t){return isNaN(t=+t)?0:(t>0?it:ot)(t)},ct=Math.min,at=function(t){return t>0?ct(ut(t),9007199254740991):0},ft=Math.max,st=Math.min,lt=function(t,n){var e=ut(t);return e<0?ft(e+n,0):st(e,n)},pt=function(t){return function(n,e,r){var o,i=v(n),u=at(i.length),c=lt(r,u);if(t&&e!=e){for(;u>c;)if((o=i[c++])!=o)return!0}else for(;u>c;c++)if((t||c in i)&&i[c]===e)return t||c||0;return!t&&-1}},dt={includes:pt(!0),indexOf:pt(!1)},yt=dt.indexOf,vt=function(t,n){var e,r=v(t),o=0,i=[];for(e in r)!b(W,e)&&b(r,e)&&i.push(e);for(;n.length>o;)b(r,e=n[o++])&&(~yt(i,e)||i.push(e));return i},gt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],ht=gt.concat("length","prototype"),mt={f:Object.getOwnPropertyNames||function(t){return vt(t,ht)}},bt={f:Object.getOwnPropertySymbols},wt=rt("Reflect","ownKeys")||function(t){var n=mt.f(T(t)),e=bt.f;return e?n.concat(e(t)):n},St=function(t,n){for(var e=wt(n),r=P.f,o=E.f,i=0;ii;)P.f(t,e=r[i++],n[e]);return t},Dt=rt("document","documentElement"),Ft=K("IE_PROTO"),Bt=function(){},Rt=function(t){return"