diff --git a/.deploy.enc b/.deploy.enc index b22144e3..178d80b2 100644 Binary files a/.deploy.enc and b/.deploy.enc differ diff --git a/.eslintignore b/.eslintignore index 0711ad31..62117a94 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,8 +3,5 @@ coverage/ docs/lib/ examples/ /mithril.js -/mithril.mjs /mithril.min.js -/mithril.min.mjs -/stream/stream.mjs node_modules/ diff --git a/.gitattributes b/.gitattributes index 91f1f822..7296866f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,5 @@ * text=auto /mithril.js binary /mithril.min.js binary -/mithril.mjs binary -/mithril.min.mjs binary /package-lock.json binary /yarn.lock binary diff --git a/.travis.yml b/.travis.yml index dbfc11a5..97971661 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,6 @@ before_script: - npm run build-browser # Pass -save so it'll update the readme as well - npm run build-min -- -save -# must run after build-min in order to generate min.mjs -- npm run build-esm # Run tests, lint, and then check for perf regressions script: @@ -34,8 +32,8 @@ after_success: - | # Set up SSH environment $(npm bin)/set-up-ssh \ - --key "$encrypted_8b86e0359d64_key" \ - --iv "$encrypted_8b86e0359d64_iv" \ + --key "$encrypted_016049456622_key" \ + --iv "$encrypted_016049456622_iv" \ --path-encrypted-key "./.deploy.enc" # Commit bundle changes generated in before_script step @@ -75,20 +73,18 @@ after_success: env: global: # Set up GH_USER_EMAIL & GH_USER_NAME env variables used by travis-scripts package - - secure: Xvqvm3+PvJu/rs3jl/NNn0RWLkkLkIoPHiL0GCfVRaywgjCYVN02g54NVvIDaOfybqPmu9E6PJFVs92vhF34NMFQHf4EWskynusIGV271R2BV0i+OJBfLMuLgiwm6zRn7/Zw4JvWIUGEwcnlz0qxbqdHsS0SOR3fIkFzePickW0= - - secure: Rf/ldEO9d4vItJhe6EmqWpFAyCARzoCb422nHnjr1hYJknnwIXpgyZ1C/7On/9o7rWPPf+8WcHC/rgjK2rthKCldzdG5I60LfWSNzap9lk3Aa4TpSCoDBuEp7JVvDr5tc3rKnBXVT71hOay7RSx1StWzXiJs9mjaeVMJzYzRT78= + - secure: UdSk2uKTL56iOHkIZP9Tpj/OI8w26DRTs6INRq+peZKkHq8NVC0TmbtbpRoc5kxErovN2hyqsAnoMtXSQHKK+H9mpMx0v4ck/I+o2oEny/1hwi5YQ/Q0nAebVhZkgA3eVhJY1brK0bOlr8uI07m9mcPs3Qz0ramZutJQG7FdnZs= # Deploy to npm and github pages on tagged commits that successfully build deploy: - provider: releases api_key: - secure: PauFuz+pn7oRpHn2JTl4k3+iWjOofyBYBvavPQVNdXgKws9mGj0i2n5k2oIDU09VD7NeyEkwP6tdLCUFNaR8uwTJH/TBXMZE95oxUEaliFreA0nOiI3WkG4NCW0GwUoIIn1yL14y6+9oEBinWUia8DIn9kZNS11DNDgQpIPnoQQ= + secure: BBlVwr3CRWS6Zmc8nsYMMN59P95g/lg9OzNCsP7uiFyu7mnC6VdOIgwmmJXBviUiEymbcdepgRqEdjHIoemg2YhM5IOnhMoYFrfMBoWUpMihpejFY8EVm3NrTh0prXgCHbp/3OktoKdOn/Zhc2z7cKDMeToHzaDStLtQPR8u9jU= file: - "mithril.js" - "mithril.min.js" - - "mithril.mjs" - - "mithril.min.mjs" skip_cleanup: true + draft: true on: tags: true repo: MithrilJS/mithril.js diff --git a/README.md b/README.md index 1cc98724..5088ce20 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.88 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (9.31 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess ๐Ÿ‘. @@ -29,9 +29,9 @@ Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge, ### CDN ```html - + - + ``` ### npm diff --git a/api/router.js b/api/router.js index 4d893acc..a053bac2 100644 --- a/api/router.js +++ b/api/router.js @@ -23,7 +23,7 @@ module.exports = function($window, redrawService) { if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) else throw new Error("Could not resolve default route " + defaultRoute) } - routeService.defineRoutes(routes, function(payload, params, path) { + routeService.defineRoutes(routes, function(payload, params, path, route) { var update = lastUpdate = function(routeResolver, comp) { if (update !== lastUpdate) return component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" @@ -34,13 +34,13 @@ module.exports = function($window, redrawService) { if (payload.view || typeof payload === "function") update({}, payload) else { if (payload.onmatch) { - Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { + Promise.resolve(payload.onmatch(params, path, route)).then(function(resolved) { update(payload, resolved) - }, bail) + }, function () { bail(path) }) } else update(payload, "div") } - }, bail) + }, bail, defaultRoute) } route.set = function(path, data, options) { if (lastUpdate != null) { diff --git a/api/tests/test-router.js b/api/tests/test-router.js index f5336625..cf99e4c6 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -321,11 +321,12 @@ o.spec("route", function() { } var resolver = { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") + o(route).equals("/:id") o(this).equals(resolver) return Component }, @@ -362,11 +363,12 @@ o.spec("route", function() { } var resolver = { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") + o(route).equals("/:id") o(this).equals(resolver) return Promise.resolve(Component) }, @@ -398,11 +400,12 @@ o.spec("route", function() { var renderCount = 0 var resolver = { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") + o(route).equals("/:id") o(this).equals(resolver) return Promise.resolve() }, @@ -434,11 +437,12 @@ o.spec("route", function() { var renderCount = 0 var resolver = { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") + o(route).equals("/:id") o(this).equals(resolver) return Promise.resolve([]) }, @@ -508,11 +512,12 @@ o.spec("route", function() { $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") + o(route).equals("/:id") return Component }, @@ -985,7 +990,8 @@ o.spec("route", function() { $window.location.href = prefix + "/a" route(root, "/", { "/a": {view: view}, - "/b": {onmatch: onmatch} + "/b": {onmatch: onmatch}, + "/": {view: function() {}} }) o(view.callCount).equals(1) diff --git a/docs/animation.md b/docs/animation.md index 95dd4c0c..1f7b0b92 100644 --- a/docs/animation.md +++ b/docs/animation.md @@ -9,7 +9,7 @@ ### Technology choices -Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast Javascript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. +Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work. diff --git a/docs/buildPathname.md b/docs/buildPathname.md new file mode 100644 index 00000000..35049a4c --- /dev/null +++ b/docs/buildPathname.md @@ -0,0 +1,41 @@ +# buildPathname(object) + +- [Description](#description) +- [Signature](#signature) +- [How it works](#how-it-works) + +--- + +### Description + +Turns a [path template](paths.md) and a parameters object into a string of form `/path/user?a=1&b=2` + +```javascript +var querystring = m.buildPathname("/path/:id", {id: "user", a: "1", b: "2"}) +// "/path/user?a=1&b=2" +``` + +--- + +### Signature + +`querystring = m.buildPathname(object)` + +Argument | Type | Required | Description +------------ | ------------------------------------------ | -------- | --- +`object` | `Object` | Yes | A key-value map to be converted into a string +**returns** | `String` | | A string representing the input object + +[How to read signatures](signatures.md) + +--- + +### How it works + +The `m.buildPathname` creates a [path name](paths.md) from a path template and a parameters object. It's useful for building URLs, and it's what [`m.route`](route.md), [`m.request`](request.md), and [`m.jsonp`](jsonp.md) all use internally to interpolate paths. It uses [`m.buildQueryString`](buildQueryString.md) to generate the query parameters to append to the path name. + +```javascript +var querystring = m.buildPathname("/path/:id", {id: "user", a: 1, b: 2}) + +// querystring is "/path/user?a=1&b=2" +``` diff --git a/docs/change-log.md b/docs/change-log.md index 380fd761..c0bb1199 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -10,11 +10,15 @@ - [v1.1.0](#v110) - [v1.0.1](#v101) - [Migrating from v0.2.x](#migrating-from-v02x) -- [Older docs](http://mithril.js.org/archive/v0.2.5/index.html) -- [ospec change-log](../ospec/change-log.md) +- [v1.x docs](http://mithril.js.org/archive/v1.1.6/index.html) +- [v0.2 docs](http://mithril.js.org/archive/v0.2.5/index.html) +- [`ospec` change log](https://github.com/MithrilJS/mithril.js/blob/master/ospec/change-log.md) +- [`mithril/stream` change log](https://github.com/MithrilJS/mithril.js/blob/master/stream/change-log.md) --- +### Upcoming... + ### v2.0.0-rc #### Breaking changes @@ -27,13 +31,15 @@ - hyperscript: when attributes have a `null` or `undefined` value, they are treated as if they were absent. [#1773](https://github.com/MithrilJS/mithril.js/issues/1773) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) - API: `m.request` errors no longer copy response fields to the error, but instead assign the parsed JSON response to `error.response` and the HTTP status code `error.code`. - hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) -- stream: when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200)) -- render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213)) - render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221)) -- render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214)) - cast className using toString ([#2309](https://github.com/MithrilJS/mithril.js/pull/2309)) - render: call attrs' hooks first, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297)) - API: `m.withAttr` removed. ([#2317](https://github.com/MithrilJS/mithril.js/pull/2317)) +- request: `data` has now been split to `params` and `body` and `useBody` has been removed in favor of just using `body`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) +- route, request: Interpolated arguments are URL-escaped (and for declared routes, URL-unescaped) automatically. If you want to use a raw route parameter, use a variadic parameter like in `/asset/:path.../view`. This was previously only available in `m.route` route definitions, but it's now usable in both that and where paths are accepted. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) +- route, request: Interpolated arguments are *not* appended to the query string. This means `m.request({url: "/api/user/:id/get", params: {id: user.id}})` would result in a request like `GET /api/user/1/get`, not one like `GET /api/user/1/get?id=1`. If you really need it in both places, pass the same value via two separate parameters with the non-query-string parameter renamed, like in `m.request({url: "/api/user/:urlID/get", params: {id: user.id, urlID: user.id}})`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) +- route, request: `m.route.set`, `m.request`, and `m.jsonp` all use the same path template syntax now, and vary only in how they receive their parameters. Furthermore, declared routes in `m.route` shares the same syntax and semantics, but acts in reverse as if via pattern matching. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) +- request: `options.responseType` now defaults to `"json"` if `extract` is absent, and `deserialize` receives the parsed response, not the raw string. If you want the old behavior, [use `responseType: "text"`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType). ([#2335](https://github.com/MithrilJS/mithril.js/pull/2335)) #### News @@ -49,12 +55,15 @@ - render/core: remove the DOM nodes recycling pool ([#2122](https://github.com/MithrilJS/mithril.js/pull/2122)) - render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes. - docs: Emphasize Closure Components for stateful components, use them for all stateful component examples. -- stream: Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` [#1944](https://github.com/MithrilJS/mithril.js/issues/1944) - API: ES module bundles are now available for `mithril` and `mithril/stream` ([#2194](https://github.com/MithrilJS/mithril.js/pull/2194) [@porsager](https://github.com/porsager)). - All of the `m.*` properties from `mithril` are re-exported as named exports in addition to being attached to `m`. - `m()` itself from `mithril` is exported as the default export. - `mithril/stream`'s primary export is exported as the default export. - fragments: allow same attrs/children overloading logic as hyperscript ([#2328](https://github.com/MithrilJS/mithril.js/pull/2328)) +- route: Declared routes may check against path names with query strings. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) +- route: Declared routes in `m.route` now support `-` and `.` as delimiters for path segments. This means you can have a route like `"/edit/:file.:ext"`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) + - Previously, this was possible to do in `m.route.set`, `m.request`, and `m.jsonp`, but it was wholly untested for and also undocumented. +- API: `m.buildPathname` and `m.parsePathname` added. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) #### Bug fixes @@ -75,6 +84,12 @@ - render/core: Vnodes stored in the dom node supplied to `m.render()` are now normalized [#2266](https://github.com/MithrilJS/mithril.js/pull/2266) - render/core: CSS vars can now be specified in `{style}` attributes ([#2192](https://github.com/MithrilJS/mithril.js/pull/2192) [@barneycarroll](https://github.com/barneycarroll)), ([#2311](https://github.com/MithrilJS/mithril.js/pull/2311) [@porsager](https://github.com/porsager)), ([#2312](https://github.com/MithrilJS/mithril.js/pull/2312) [@isiahmeadows](https://github.com/isiahmeadows)) - request: don't modify params, call `extract`/`serialize`/`deserialize` with correct `this` value ([#2288](https://github.com/MithrilJS/mithril.js/pull/2288)) +- render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214)) +- render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213)) +- API: `mithril` loads `mithril/index.js`, not the bundle, so users of `mithril/hyperscript`, `mithril/render`, and similar see the same Mithril instance as those just using `mithril` itself. + - `https://unpkg.com/mithril` is configured to receive the *minified* bundle, not the development bundle. + - The raw bundle itself remains accessible at `mithril.js`, and is *not* browser-wrapped. + - Note: this *will* increase overhead with bundlers like Webpack, Rollup, and Browserify. --- diff --git a/docs/components.md b/docs/components.md index 3dccc5c1..eea61ae7 100644 --- a/docs/components.md +++ b/docs/components.md @@ -14,7 +14,7 @@ Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. -Any Javascript object that has a `view` method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: +Any JavaScript object that has a `view` method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: ```javascript // define your component @@ -117,7 +117,7 @@ If a state change occurs that is not as a result of any of the above conditions #### Closure Component State -In the above examples, each component is defined as a POJO (Plain Old Javascript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. +In the above examples, each component is defined as a POJO (Plain Old JavaScript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. With a closure component, state can simply be maintained by variables that are declared within the outer function: @@ -247,7 +247,7 @@ m(ComponentUsingThis, {text: "Hello"}) //
Hello
``` -Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this Javascript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. +Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this JavaScript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. --- diff --git a/docs/contributing.md b/docs/contributing.md index 8489707f..e656984f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -114,9 +114,9 @@ Type checks are generally already irreducible expressions and having micro-modul ## What should I know in advance when attempting a performance related contribution? -You should be trying to reduce the number of DOM operations or reduce algorithmic complexity in a hot spot. Anything else is likely a waste of time. Specifically, micro-optimizations like caching array lengths, caching object property values and inlining functions won't have any positive impact in modern javascript engines. +You should be trying to reduce the number of DOM operations or reduce algorithmic complexity in a hot spot. Anything else is likely a waste of time. Specifically, micro-optimizations like caching array lengths, caching object property values and inlining functions won't have any positive impact in modern JavaScript engines. -Keep object properties consistent (i.e. ensure the data objects always have the same properties and that properties are always in the same order) to allow the engine to keep using JIT'ed structs instead of hashmaps. Always place null checks first in compound type checking expressions to allow the Javascript engine to optimize to type-specific code paths. Prefer for loops over Array methods and try to pull conditionals out of loops if possible. +Keep object properties consistent (i.e. ensure the data objects always have the same properties and that properties are always in the same order) to allow the engine to keep using JIT'ed structs instead of hashmaps. Always place null checks first in compound type checking expressions to allow the JavaScript engine to optimize to type-specific code paths. Prefer for loops over Array methods and try to pull conditionals out of loops if possible. diff --git a/docs/es6.md b/docs/es6.md index 0192a6ca..75b9fdf7 100644 --- a/docs/es6.md +++ b/docs/es6.md @@ -5,7 +5,7 @@ --- -Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. +Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to JavaScript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5. diff --git a/docs/fragment.md b/docs/fragment.md index 5a914165..f6269d07 100644 --- a/docs/fragment.md +++ b/docs/fragment.md @@ -67,6 +67,6 @@ m("ul", ) ``` -However, Javascript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. +However, JavaScript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. There are a few benefits that come from using `m.fragment` instead of handwriting a vnode object structure: m.fragment creates [monomorphic objects](vnodes.md#monomorphic-class), which have better performance characteristics than creating objects dynamically. In addition, using `m.fragment` makes your intentions clear to other developers, and it makes it less likely that you'll mistakenly set attributes on the vnode object itself rather than on its `attrs` map. diff --git a/docs/framework-comparison.md b/docs/framework-comparison.md index 0969b2c9..73bd96b0 100644 --- a/docs/framework-comparison.md +++ b/docs/framework-comparison.md @@ -38,7 +38,7 @@ React and Mithril share a lot of similarities. If you already learned React, you - They both use virtual DOM, lifecycle methods and key-based reconciliation - They both organize views via components -- They both use Javascript as a flow control mechanism within views +- They both use JavaScript as a flow control mechanism within views The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project, and that those projects are that much more likely to cross the 1MB size line. @@ -50,7 +50,7 @@ Both React and Mithril care strongly about rendering performance, but go about i Mithril follows the less-is-more school of thought. It has a substantially smaller, aggressively optimized codebase. The rationale is that a small codebase is easier to audit and optimize, and ultimately results in less code being run. -Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: +Here's a comparison of library load times, i.e. the time it takes to parse and run the JavaScript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: React | Mithril ------- | ------- @@ -74,7 +74,7 @@ What these numbers show is that not only does Mithril initializes significantly Update performance can be even more important than first-render performance, since updates can happen many times while a Single Page Application is running. -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: React | Mithril ------- | ------- @@ -125,7 +125,7 @@ Angular and Mithril are fairly different, but they share a few similarities: - Both support componentization - Both have an array of tools for various aspects of web applications (e.g. routing, XHR) -The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain Javascript, and flow control is done with Javascript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate Javascript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in Javascript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates Javascript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions). +The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain JavaScript, and flow control is done with JavaScript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate JavaScript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in JavaScript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates JavaScript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions). #### Performance @@ -139,7 +139,7 @@ Also, remember that frameworks like Angular and Mithril are designed for non-tri ##### Update performance -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Angular | Mithril ------- | ------- @@ -155,7 +155,7 @@ Angular 2 has a lot more concepts to understand: on the language level, Typescri If we compare apples to apples, Angular 2 and Mithril have similar learning curves: in both, components are a central aspect of architecture, and both have reasonable routing and XHR tools. -With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what Javascript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about. +With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what JavaScript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about. #### Documentation @@ -183,7 +183,7 @@ Vue is significantly smaller than Angular when comparing apples to apples, but n #### Performance -Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: +Here's a comparison of library load times, i.e. the time it takes to parse and run the JavaScript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: Vue | Mithril ------- | ------- @@ -193,7 +193,7 @@ Library load times matter in applications that don't stay open for long periods ##### Update performance -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and JavaScript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Vue | Mithril ------ | ------- @@ -203,7 +203,7 @@ Vue | Mithril Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, bi-directional bindings, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, but unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects. -Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. +Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native JavaScript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. #### Documentation diff --git a/docs/hyperscript.md b/docs/hyperscript.md index 4adb8516..bd718b1c 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -55,7 +55,7 @@ Argument | Type | Required | Descripti ### How it works -Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attrs` object (optional) and a `children` array (optional). +Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using JavaScript syntax. It accepts a `selector` string (required), an `attrs` object (optional) and a `children` array (optional). ```javascript m("div", {id: "box"}, "hello") @@ -64,7 +64,7 @@ m("div", {id: "box"}, "hello") //
hello
``` -The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a javascript object that represents the DOM element to be created. +The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a JavaScript object that represents the DOM element to be created. ```javascript // a vnode @@ -167,9 +167,9 @@ If another attribute is present in both the first and the second argument, the s ### DOM attributes -Mithril uses both the Javascript API and the DOM API (`setAttribute`) to resolve attributes. This means you can use both syntaxes to refer to attributes. +Mithril uses both the JavaScript API and the DOM API (`setAttribute`) to resolve attributes. This means you can use both syntaxes to refer to attributes. -For example, in the Javascript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported: +For example, in the JavaScript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported: ```javascript m("input", {readonly: true}) // lowercase @@ -322,7 +322,7 @@ m("select", {selectedIndex: 0}, [ [Components](components.md) allow you to encapsulate logic into a unit and use it as if it was an element. They are the base for making large, scalable applications. -A component is any Javascript object that contains a `view` method. To consume a component, pass the component as the first argument to `m()` instead of passing a CSS selector string. You can pass arguments to the component by defining attributes and children, as shown in the example below. +A component is any JavaScript object that contains a `view` method. To consume a component, pass the component as the first argument to `m()` instead of passing a CSS selector string. You can pass arguments to the component by defining attributes and children, as shown in the example below. ```javascript // define a component @@ -413,7 +413,7 @@ MathML is also fully supported. ### Making templates dynamic -Since nested vnodes are just plain Javascript expressions, you can simply use Javascript facilities to manipulate them +Since nested vnodes are just plain JavaScript expressions, you can simply use JavaScript facilities to manipulate them #### Dynamic text @@ -454,7 +454,7 @@ var isError = false m("div", isError ? "An error occurred" : "Saved") //
Saved
``` -You cannot use Javascript statements such as `if` or `for` within Javascript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative, and to avoid deoptimizations. +You cannot use JavaScript statements such as `if` or `for` within JavaScript expressions. It's preferable to avoid using those statements altogether and instead, use the constructs above exclusively in order to keep the structure of the templates linear and declarative, and to avoid deoptimizations. --- @@ -520,7 +520,7 @@ var BetterLabeledComponent = { #### Avoid statements in view methods -Javascript statements often require changing the naturally nested structure of an HTML tree, making the code more verbose and harder to understand. Constructing an virtual DOM tree procedurally can also potentially trigger expensive deoptimizations (such as an entire template being recreated from scratch) +JavaScript statements often require changing the naturally nested structure of an HTML tree, making the code more verbose and harder to understand. Constructing an virtual DOM tree procedurally can also potentially trigger expensive deoptimizations (such as an entire template being recreated from scratch) ```javascript // AVOID @@ -536,7 +536,7 @@ var BadListComponent = { } ``` -Instead, prefer using Javascript expressions such as the ternary operator and Array methods. +Instead, prefer using JavaScript expressions such as the ternary operator and Array methods. ```javascript // PREFER diff --git a/docs/index.md b/docs/index.md index 2df235d4..d2ea949e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ ### What is Mithril? -Mithril is a modern client-side Javascript framework for building Single Page Applications. +Mithril is a modern client-side JavaScript framework for building Single Page Applications. It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box.
@@ -58,7 +58,7 @@ Let's create an HTML file to follow along: ```markup - + + ``` --- @@ -71,7 +71,7 @@ $ npm start For production-level projects, the recommended way of installing Mithril is to use NPM. -NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the Javascript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well. +NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well. To use Mithril via NPM, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`. @@ -99,9 +99,9 @@ m.render(document.body, "hello world") Modularization is the practice of separating the code into files. Doing so makes it easier to find code, understand what code relies on what code, and test. -CommonJS is a de-facto standard for modularizing Javascript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/), [Babel](https://babeljs.io/) or [Traceur](https://github.com/google/traceur-compiler). +CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/), [Babel](https://babeljs.io/) or [Traceur](https://github.com/google/traceur-compiler). -Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single Javascript file before running in a client-side application. +Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application. A popular way for creating a bundle is to setup an NPM script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line: @@ -247,7 +247,7 @@ If you don't have the ability to run a bundler script due to company security po Hello world - + diff --git a/docs/integrating-libs.md b/docs/integrating-libs.md index 2a45a196..904f1127 100644 --- a/docs/integrating-libs.md +++ b/docs/integrating-libs.md @@ -1,6 +1,6 @@ # 3rd Party Integration -Integration with third party libraries or vanilla javascript code can be achieved via [lifecycle methods](lifecycle-methods.md). +Integration with third party libraries or vanilla JavaScript code can be achieved via [lifecycle methods](lifecycle-methods.md). ## noUiSlider Example @@ -66,13 +66,13 @@ m.mount(document.body, Demo) [Live Demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoJAAgBytAKoQAylAgATGACdpAdy0YADoe3S6WQ-RHSJYgDpowAVzTViEetNUbtACgCU0sAO0tIAbhg6cGqaWg4h0lowxE5aaEEJofTUSRiMiNLOru70vmFotJqBwemhdWJi0gCSaBDuGGoAXjDSAMxa6tKGkcQAntJqAEbShNowmXXRPjoAvNIVSt6x+DkweTBlFZr46rRYFBm1dYvEIwUADBQL1wZoAOYwBcBYEGgPF1gMAAPAoARnu91Yz2krH80KW21KAHInIZ1PskRcim4PGgyh0nPBqtDQuVKjB8HliFo4Ph6ABhQgYd4HCJQQlwZD3AC6cKu12kWHwSXUBl0AWhsIW7AW9CSWFoYU+hRcONKxP5oQa0npsGZqL6AyGI3GU2knnlio68Ji2hO8GptFGEv5Mv5YQgMF0BWxJTxGoFiWSqXSWF8SPUEDCSL51yhtXj8YckhkABEYArpEZDGYzpY0NZbA5fbjpOmFQFLqFYMRpHBCLRdFtTGswB04PNajXwgSemt7vFakkUmkq3UPV6faq-ZWaoHhyHBeHKcZMSSl0jDGvNQKw0jJk5iMR6NvA4G52fA2MTAV94fj2hT5eBdk1NQANZT4q42fry-1xtm1WaQAEIAKbW04h3S942fOo3Tg0JwKA6QAH5pDsEB0zgR1xiAzDpAKTD6VyRgvEgzC-2kWMz38J5oLrBsIOWaQADJWKXICLgvS8GSZFkvzVPEwgDRC2UJaQ1jCKjYLPWF6MvPctwucSYBo651JhBJE0HIUFRcYhfFOagnBwBh8EmSpRguctaD5NgOBATAcDwHY4AEGh6EYZgeDYbkqDUNB3wQFBOBcngKicCAEW0SgQFScgeBIYhDDgRAGhcQx3zeHYzjESLosggABUEACZ8FBfB7jESMcK0CAD0YfLaCimKtHwfg4uvbgQDgHIIEMUR2DCnqCratyPISvBktS9KxEy7LcqwZrWuKsqKqqmroupBrDxgFbCuWCautGEw8Bw0ZYAcka8B+YhCHq8gqCmpKj1mjK0CynLzDEO6HugEqNoANl+tp-qgDqPO687+sGvzWCAA) -## Bootstrap FullCalandar Example +## Bootstrap FullCalendar Example ```javascript /** FullCalendar wrapper component */ var FullCalendar = { oncreate: function (vnode) { - console.log('FullCalndar::oncreate') + console.log('FullCalendar::oncreate') $(vnode.dom).fullCalendar({ // put your initial options and callbacks here }) diff --git a/docs/jsonp.md b/docs/jsonp.md index 0a5997fd..45ca1671 100644 --- a/docs/jsonp.md +++ b/docs/jsonp.md @@ -26,12 +26,12 @@ m.jsonp({ ### Signature -`promise = m.jsonp([url,] options)` +`promise = m.jsonp(options)` Argument | Type | Required | Description ---------------------- | --------------------------------- | -------- | --- -`url` | `String` | No | If present, it's equivalent to having the option `{url: url}`. Values passed to the `options` argument override options set via this shorthand. -`options.url` | `String` | Yes | The URL to send the request to. The URL may be either absolute or relative, and it may contain [interpolations](#dynamic-urls). +`options` | `Object` | Yes | The request options to pass. +`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`. `options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring. `options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). `options.callbackName` | `String` | No | The name of the function that will be called as the callback. Defaults to a randomized string (e.g. `_mithril_6888197422121285_0({a: 1})` @@ -39,6 +39,16 @@ Argument | Type | Required | Descript `options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`. **returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through `type` method +`promise = m.jsonp(url, options)` + +Argument | Type | Required | Description +----------- | --------- | -------- | --- +`url` | `String` | Yes | The [path name](paths.md) to send the request to. `options.url` overrides this when present. +`options` | `Object` | No | The request options to pass. +**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `type` method + +This second form is mostly equivalent to `m.jsonp(Object.assign({url: url}, options))`, just it does not depend on the ES6 global `Object.assign` internally. + [How to read signatures](signatures.md) --- @@ -49,7 +59,7 @@ The `m.jsonp` utility is useful for third party APIs that can return data in [JS In a nutshell, JSON-P consists of creating a `script` tag whose `src` attribute points to a script that lives in the server outside of your control. Typically, you are required to define a global function and specify its name in the querystring of the script's URL. The response will return code that calls your global function, passing the server's data as the first parameter. -JSON-P has several limitations: it can only use GET requests, it implicitly trusts that the third party server won't serve malicious code and it requires polluting the global Javascript scope. Nonetheless, it is sometimes the only available way to retrieve data from a service (for example, if the service doesn't support [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)). +JSON-P has several limitations: it can only use GET requests, it implicitly trusts that the third party server won't serve malicious code and it requires polluting the global JavaScript scope. Nonetheless, it is sometimes the only available way to retrieve data from a service (for example, if the service doesn't support [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)). --- @@ -87,4 +97,3 @@ m.jsonp({ console.log(response.data.login) // logs "lhorie" }) ``` - diff --git a/docs/jsx.md b/docs/jsx.md index 8c715072..01ddbd30 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -10,7 +10,7 @@ ### Description -JSX is a syntax extension that enables you to write HTML tags interspersed with Javascript. It's not part of any Javascript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences. +JSX is a syntax extension that enables you to write HTML tags interspersed with JavaScript. It's not part of any JavaScript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences. ```jsx var MyComponent = { @@ -33,7 +33,7 @@ var MyComponent = { } ``` -When using JSX, it's possible to interpolate Javascript expressions within JSX tags by using curly braces: +When using JSX, it's possible to interpolate JavaScript expressions within JSX tags by using curly braces: ```jsx var greeting = "Hello" @@ -186,14 +186,14 @@ JSX is essentially a trade-off: it introduces a non-standard syntax that cannot Unlike HTML, JSX is case-sensitive. This means `
` is different from `
` (all lower case). The former compiles to `m("div", {className: "test"})` and the latter compiles to `m("div", {classname: "test"})`, which is not a valid way of creating a class attribute. Fortunately, Mithril supports standard HTML attribute names, and thus, this example can be written like regular HTML: `
`. -JSX is useful for teams where HTML is primarily written by someone without Javascript experience, but it requires a significant amount of tooling to maintain (whereas plain HTML can, for the most part, simply be opened in a browser) +JSX is useful for teams where HTML is primarily written by someone without JavaScript experience, but it requires a significant amount of tooling to maintain (whereas plain HTML can, for the most part, simply be opened in a browser) Hyperscript is the compiled representation of JSX. It's designed to be readable and can also be used as-is, instead of JSX (as is done in most of the documentation). Hyperscript tends to be terser than JSX for a couple of reasons: - it does not require repeating the tag name in closing tags (e.g. `m("div")` vs `
`) - static attributes can be written using CSS selector syntax (i.e. `m("a.button")` vs ``) -In addition, since hyperscript is plain Javascript, it's often more natural to indent than JSX: +In addition, since hyperscript is plain JavaScript, it's often more natural to indent than JSX: ```jsx //JSX @@ -239,9 +239,9 @@ var BigComponent = { } ``` -In non-trivial applications, it's possible for components to have more control flow and component configuration code than markup, making a Javascript-first approach more readable than an HTML-first approach. +In non-trivial applications, it's possible for components to have more control flow and component configuration code than markup, making a JavaScript-first approach more readable than an HTML-first approach. -Needless to say, since hyperscript is pure Javascript, there's no need to run a compilation step to produce runnable code. +Needless to say, since hyperscript is pure JavaScript, there's no need to run a compilation step to produce runnable code. --- diff --git a/docs/mount.md b/docs/mount.md index 19a0110a..eacb03b7 100644 --- a/docs/mount.md +++ b/docs/mount.md @@ -65,11 +65,11 @@ Using `m.mount(element, null)` on an element with a previously mounted component ### Performance considerations -It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing Javascript data structures is surprisingly cheap compared to reading and modifying the DOM. +It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing JavaScript data structures is surprisingly cheap compared to reading and modifying the DOM. Touching the DOM can be extremely expensive for a couple of reasons. Alternating reads and writes can adversely affect performance by causing several browser repaints to occur in quick succession, whereas comparing virtual dom trees allows writes to be batched into a single repaint. Also, the performance characteristics of various DOM operations vary between implementations and can be difficult to learn and optimize for all browsers. For example, in some implementations, reading `childNodes.length` has a complexity of O(n); in some, reading `parentNode` causes a repaint, etc. -In contrast, traversing a javascript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern javascript engines to apply aggressive optimizations such as hidden classes for even better performance. +In contrast, traversing a JavaScript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern JavaScript engines to apply aggressive optimizations such as hidden classes for even better performance. --- diff --git a/docs/nav-guides.md b/docs/nav-guides.md index 5b1016b5..e8ac4234 100644 --- a/docs/nav-guides.md +++ b/docs/nav-guides.md @@ -12,6 +12,7 @@ - [Testing](testing.md) - [Examples](examples.md) - [3rd Party Integration](integrating-libs.md) + - [Path Handling](paths.md) - Key concepts - [Vnodes](vnodes.md) - [Components](components.md) diff --git a/docs/parsePathname.md b/docs/parsePathname.md new file mode 100644 index 00000000..7e5db965 --- /dev/null +++ b/docs/parsePathname.md @@ -0,0 +1,42 @@ +# parsePathname(string) + +- [Description](#description) +- [Signature](#signature) +- [How it works](#how-it-works) + +--- + +### Description + +Turns a string of the form `/path/user?a=1&b=2` to an object + +```javascript +var object = m.parsePathname("/path/user?a=1&b=2") +// {path: "/path/user", params: {a: "1", b: "2"}} +``` + +--- + +### Signature + +`object = m.parsePathname(string)` + +Argument | Type | Required | Description +------------ | -------- | -------- | --- +`string` | `String` | Yes | A URL +**returns** | `Object` | | A `{path, params}` pair where `path` is the [normalized path](paths.md#path-normalization) and `params` is the [parsed parameters](paths.md#parameter-normalization). + +[How to read signatures](signatures.md) + +--- + +### How it works + +The `m.parsePathname` method creates an object from a path with a possible query string and hash string. It is useful for parsing a URL into more sensible paths, and it's what [`m.route`](route.md) uses internally to normalize paths to later match them. It uses [`m.parseQueryString`](parseQueryString.md) to parse the query parameters into an object. + +```javascript +var data = m.parsePathname("/path/user?a=hello&b=world#random=hash&some=value") + +// data.path is "/path/user" +// data.params is {a: "hello", b: "world", random: "hash", some: "value"} +``` diff --git a/docs/parseQueryString.md b/docs/parseQueryString.md index e3d23dcb..8432a338 100644 --- a/docs/parseQueryString.md +++ b/docs/parseQueryString.md @@ -19,12 +19,13 @@ var object = m.parseQueryString("a=1&b=2") ### Signature -`object = m.parseQueryString(string)` +`object = m.parseQueryString(string, object)` Argument | Type | Required | Description ------------ | ------------------------------------------ | -------- | --- `string` | `String` | Yes | A querystring -**returns** | `Object` | | A key-value map +`object` | `Object` | No | An existing key-value map to merge values into, potentially from a previous `m.parseQueryString` call +**returns** | `Object` | | A key-value map, `object` if provided [How to read signatures](signatures.md) @@ -68,4 +69,4 @@ Querystrings that contain bracket notation are correctly parsed into deep data s m.parseQueryString("a[0]=hello&a[1]=world") // data is {a: ["hello", "world"]} -``` \ No newline at end of file +``` diff --git a/docs/paths.md b/docs/paths.md new file mode 100644 index 00000000..fc7c0a56 --- /dev/null +++ b/docs/paths.md @@ -0,0 +1,132 @@ +# Path Handling + +- [Path types](#path-types) +- [Path parameters](#path-parameters) +- [Parameter normalization](#parameter-normalization) +- [Path normalization](#path-normalization) + +----- + +[`m.route`](route.md), [`m.request`](request.md), and [`m.jsonp`](jsonp.md) each have a concept called a path. This is used to generate the URL you route to or fetch from. + +### Path types + +There are two general types of paths: raw paths and parameterized paths. + +- Raw paths are simply strings used directly as URLs. Nothing is substituted or even split. It's just normalized with all the parameters appended to the end. +- Parameterized paths let you insert values into paths, escaped by default for convenience and safety against URL injection. + +For [`m.request`](request.md) and [`m.jsonp`](jsonp.md), these can be pretty much any URL, but for [routes](route.md), these can only be absolute URL path names without schemes or domains. + +### Path parameters + +Path parameters are themselves pretty simple. They come in two forms: + +- `:foo` - This injects a simple `params.foo` into the URL, escaping its value first. +- `:foo...` - This injects a raw `params.foo` path into the URL without escaping anything. + +You're probably wondering what that `params` object is supposed to be. It's pretty simple: it's the `params` in either [`m.route.set(path, params)`](route.md#mrouteset), [`m.request({url, params})`](request.md#signature), or [`m.jsonp({url, params})`](jsonp.md#signature). + +When receiving routes via [`m.route(root, defaultRoute, routes)`](route.md#signature), you can use these parameters to *extract* values from routes. They work basically the same way as generating the paths, just in the opposite direction. + +```javascript +// Edit a single item +m.route(document.body, "/edit/1", { + "/edit/:id": { + view: function() { + return [ + m(Menu), + m("h1", "Editing user " + m.route.param("id")) + ] + } + }, +}) + +// Edit an item identified by path +m.route(document.body, "/edit/pictures/image.jpg", { + "/edit/:file...": { + view: function() { + return [ + m(Menu), + m("h1", "Editing file " + m.route.param("file")) + ] + } + }, +}) +``` + +In the first example, assuming you're navigating to the default route in each, `m.route.param("id")` would be read as `"1"` and `m.route.param("file")` would be read as `pictures/image.jpg`. + +Path parameters may be delimited by either a `/`, `-`, or `.`. This lets you have dynamic path segments, and they're considerably more flexible than just a path name. For example, you could match against routes like `"/edit/:name.:ext"` for editing based on file extension or `"/:lang-:region/view"` for a localized route. + +Path parameters are greedy: given a declared route `"/edit/:name.:ext"`, if you navigate to `/edit/file.test.png`, the parameters extracted will be `{name: "file.test", ext: "png"}`, not `{name: "file", ext: "test.png"}`. Similarly, given `"/route/:path.../view/:child..."`, if you go to `/route/foo/view/bar/view/baz`, the parameters extracted will be `{path: "foo/view/bar", child: "baz"}`. + +### Parameter normalization + +Path parameters that are interpolated into path names are omitted from the query string, for convenience and to keep the path name reasonably readable. For example, this sends a server request of `GET /api/user/1/connections?sort=name-asc`, omitting the duplicate `id=1` in the URL string. + +```javascript +m.request({ + url: "https://example.com/api/user/:userID/connections", + params: { + userID: 1, + sort: "name-asc" + } +}) +``` + +You can also specify parameters explicitly in the query string itself, such as in this, which is equivalent to the above: + +```javascript +m.request({ + url: "https://example.com/api/user/:userID/connections?sort=name-asc", + params: { + userID: 1 + } +}) +``` + +And of course, you can mix and match. This fires a request to `GET /api/user/1/connections?sort=name-asc&first=10`. + +```javascript +m.request({ + url: "https://example.com/api/user/:userID/connections?sort=name-asc", + params: { + userID: 1, + first: 10 + } +}) +``` + +This even extends to route matching: you can match against a route *with* explicit query strings. It retains the matched parameter for convenience, so you can still access them via vnode parameters or via [`m.route.param`](route.md#mrouteparam). Note that although this *is* possible, it's not generally recommended, since you should prefer paths for pages. It could sometimes useful if you need to generate a somewhat different view just for a particular file type, but it still logically is a query-like parameter, not a whole separate page. + +```javascript +// Note: this is generally *not* recommended - you should prefer paths for route +// declarations, not query strings. +m.route(document.body, "/edit/1", { + "/edit?type=image": { + view: function() { + return [ + m(Menu), + m("h1", "Editing photo") + ] + } + }, + "/edit": { + view: function() { + return [ + m(Menu), + m("h1", "Editing " + m.route.param("type")) + ] + } + } +}) +``` + +Note that query parameters are implicit - you don't need to name them to accept them. You can match based on an existing value, like in `"/edit?type=image"`, but you don't need to use `"/edit?type=:type"` to accept the value. In fact, Mithril would treat that as you trying to literally match against `m.route.param("type") === ":type"`. Or in summary, use `m.route.param("key")` to extract parameters - it simplifies things. + +### Path normalization + +Parsed paths are always returned with all the duplicate parameters and extra slashes dropped, and they always start with a slash. These little differences often get in the way, and it makes routing and path handling a lot more complicated than it should be. Mithril internally normalizes paths for routing, but it does not expose the current, normalized route directly. (You could compute it via [`m.parsePathname(m.route.get()).path`](parsePathname.md).) + +When parameters are deduplicated during matching, parameters in the query string are preferred over parameters in the path name, and parameters towards the end of the URL are preferred over parameters closer to the start of the URL. diff --git a/docs/promise.md b/docs/promise.md index dc93f92d..5787a0e1 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -160,7 +160,7 @@ promise.then(function(value) { Promises are useful for working with asynchronous APIs, such as [`m.request`](request.md) -Asynchronous APIs are those which typically take a long time to run, and therefore would take too long to return a value using the `return` statement of a function. Instead, they do their work in the background, allowing other Javascript code to run in the meantime. When they are done, they call a function with their results. +Asynchronous APIs are those which typically take a long time to run, and therefore would take too long to return a value using the `return` statement of a function. Instead, they do their work in the background, allowing other JavaScript code to run in the meantime. When they are done, they call a function with their results. The `m.request` function takes time to run because it makes an HTTP request to a remote server and has to wait for a response, which may take several milliseconds due to network latency. @@ -308,4 +308,4 @@ Callbacks are another mechanism for working with asynchronous computations, and However, for asynchronous computations that only occur once in response to an action, promises can be refactored more effectively, reducing code smells known as pyramids of doom (deeply nested series of callbacks with unmanaged state being used across several closure levels). -In addition, promises can considerably reduce boilerplate related to error handling. \ No newline at end of file +In addition, promises can considerably reduce boilerplate related to error handling. diff --git a/docs/render.md b/docs/render.md index 534a9d45..0e2af728 100644 --- a/docs/render.md +++ b/docs/render.md @@ -44,11 +44,11 @@ The `m.render(element, vnodes)` method takes a virtual DOM tree (typically gener ### Why Virtual DOM -It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing Javascript data structures is surprisingly cheap compared to reading and modifying the DOM. +It may seem wasteful to generate a vnode tree on every redraw, but as it turns out, creating and comparing JavaScript data structures is surprisingly cheap compared to reading and modifying the DOM. Touching the DOM can be extremely expensive for a couple of reasons. Alternating reads and writes can adversely affect performance by causing several browser repaints to occur in quick succession, whereas comparing virtual dom trees allows writes to be batched into a single repaint. Also, the performance characteristics of various DOM operations vary between implementations and can be difficult to learn and optimize for all browsers. For example, in some implementations, reading `childNodes.length` has a complexity of O(n); in some, reading `parentNode` causes a repaint, etc. -In contrast, traversing a javascript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern javascript engines to apply aggressive optimizations such as hidden classes for even better performance. +In contrast, traversing a JavaScript data structure has a much more predictable and sane performance profile, and in addition, a vnode tree is implemented in such a way that enables modern JavaScript engines to apply aggressive optimizations such as hidden classes for even better performance. --- diff --git a/docs/request.md b/docs/request.md index 6a68333f..d805f671 100644 --- a/docs/request.md +++ b/docs/request.md @@ -38,30 +38,40 @@ m.request({ ### Signature -`promise = m.request([url,] options)` +`promise = m.request(options)` Argument | Type | Required | Description ------------------------- | --------------------------------- | -------- | --- -`url` | `String` | No | If present, it's equivalent to having the options `{method: "GET", url: url}`. Values passed to the `options` argument override options set via this shorthand. +`options` | `Object` | Yes | The request options to pass. `options.method` | `String` | No | The HTTP method to use. This value should be one of the following: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` or `OPTIONS`. Defaults to `GET`. -`options.url` | `String` | Yes | The URL to send the request to. The URL may be either absolute or relative, and it may contain [interpolations](#dynamic-urls). +`options.url` | `String` | Yes | The [path name](paths.md) to send the request to, optionally interpolated with values from `options.data`. `options.data` | `any` | No | The data to be interpolated into the URL and serialized into the querystring (for GET requests) or body (for other types of requests). `options.async` | `Boolean` | No | Whether the request should be asynchronous. Defaults to `true`. `options.user` | `String` | No | A username for HTTP authorization. Defaults to `undefined`. `options.password` | `String` | No | A password for HTTP authorization. Defaults to `undefined`. This option is provided for `XMLHttpRequest` compatibility, but you should avoid using it because it sends the password in plain text over the network. `options.withCredentials` | `Boolean` | No | Whether to send cookies to 3rd party domains. Defaults to `false` `options.timeout` | `Number` | No | The amount of milliseconds a request can take before automatically being [terminated](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout). Defaults to `undefined`. -`options.responseType` | `String` | No | The expected [type](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) of the response. Defaults to `undefined`. +`options.responseType` | `String` | No | The expected [type](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) of the response. Defaults to `""` if `extract` is defined, `"json"` if missing. If `responseType: "json"`, it internally performs `JSON.parse(responseText)`. `options.config` | `xhr = Function(xhr)` | No | Exposes the underlying XMLHttpRequest object for low-level configuration. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). `options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`). `options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). `options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`). -`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the `xhr.responseText`. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. If `extract` is defined, `deserialize` will be skipped. -`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will be left as-is when the promise resolves. Furthermore, when an extract callback is provided, exceptions are *not* thrown when the server response status code indicates an error. +`options.deserialize` | `any = Function(any)` | No | A deserialization method to be applied to the `xhr.response` or normalized `xhr.responseText`. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). If `extract` is defined, `deserialize` will be skipped. +`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `options.deserialize(parsedResponse)`, throwing an exception when the server response status code indicates an error or when the response is syntactically invalid. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will be left as-is when the promise resolves. `options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods. `options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`. **returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods +`promise = m.request(url, options)` + +Argument | Type | Required | Description +----------- | --------- | -------- | --- +`url` | `String` | Yes | The [path name](paths.md) to send the request to. `options.url` overrides this when present. +`options` | `Object` | No | The request options to pass. +**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods + +This second form is mostly equivalent to `m.request(Object.assign({url: url}, options))`, just it does not depend on the ES6 global `Object.assign` internally. + [How to read signatures](signatures.md) --- @@ -82,7 +92,7 @@ m.request({ A call to `m.request` returns a [promise](promise.md) and triggers a redraw upon completion of its promise chain. -By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array). +By default, `m.request` assumes the response is in JSON format and parses it into a JavaScript object (or array). If the HTTP response status code indicates an error, the returned Promise will be rejected. Supplying an extract callback will prevent the promise rejection. @@ -124,7 +134,7 @@ m.route(document.body, "/", { Let's assume making a request to the server URL `/api/items` returns an array of objects in JSON format. -When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that Javascript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `
`s containing the titles of each todo. +When `m.route` is called at the bottom, the `Todos` component is initialized. `oninit` is called, which calls `m.request`. This retrieves an array of objects from the server asynchronously. "Asynchronously" means that JavaScript continues running other code while it waits for the response from server. In this case, it means `fetch` returns, and the component is rendered using the original empty array as `Data.todos.list`. Once the request to the server completes, the array of objects `items` is assigned to `Data.todos.list` and the component is rendered again, yielding a list of `
`s containing the titles of each todo. --- @@ -464,7 +474,7 @@ The parameter to `options.extract` is the XMLHttpRequest object once its operati Many server-side frameworks provide a view engine that interpolates database data into a template before serving HTML (on page load or via AJAX) and then employ jQuery to handle user interactions. -By contrast, Mithril is framework designed for thick client applications, which typically download templates and data separately and combine them in the browser via Javascript. Doing the templating heavy-lifting in the browser can bring benefits like reducing operational costs by freeing server resources. Separating templates from data also allow template code to be cached more effectively and enables better code reusability across different types of clients (e.g. desktop, mobile). Another benefit is that Mithril enables a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) UI development paradigm, which greatly simplifies development and maintenance of complex user interactions. +By contrast, Mithril is framework designed for thick client applications, which typically download templates and data separately and combine them in the browser via JavaScript. Doing the templating heavy-lifting in the browser can bring benefits like reducing operational costs by freeing server resources. Separating templates from data also allow template code to be cached more effectively and enables better code reusability across different types of clients (e.g. desktop, mobile). Another benefit is that Mithril enables a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) UI development paradigm, which greatly simplifies development and maintenance of complex user interactions. By default, `m.request` expects response data to be in JSON format. In a typical Mithril application, that JSON data is then usually consumed by a view. @@ -504,7 +514,7 @@ In typical scenarios, streaming won't provide noticeable performance benefits be #### Promises are not the response data -The `m.request` method returns a [Promise](promise.md), not the response data itself. It cannot return that data directly because an HTTP request may take a long time to complete (due to network latency), and if Javascript waited for it, it would freeze the application until the data was available. +The `m.request` method returns a [Promise](promise.md), not the response data itself. It cannot return that data directly because an HTTP request may take a long time to complete (due to network latency), and if JavaScript waited for it, it would freeze the application until the data was available. ```javascript // AVOID diff --git a/docs/route.md b/docs/route.md index 573ea23c..a0f5748f 100644 --- a/docs/route.md +++ b/docs/route.md @@ -71,7 +71,7 @@ Redirects to a matching route, or to the default route if no matching routes can Argument | Type | Required | Description ----------------- | --------- | -------- | --- -`path` | `String` | Yes | The path to route to, without a prefix. The path may include slots for routing parameters +`path` | `String` | Yes | The [path name](paths.md) to route to, without a prefix. The path may include parameters, interpolated with values from `data`. `data` | `Object` | No | Routing parameters. If `path` has routing parameter slots, the properties of this object are interpolated into the path string `options.replace` | `Boolean` | No | Whether to create a new history entry or to replace the current one. Defaults to false `options.state` | `Object` | No | The `state` object to pass to the underlying `history.pushState` / `history.replaceState` call. This state object becomes available in the `history.state` property, and is merged into the [routing parameters](#routing-parameters) object. Note that this option only works when using the pushState API, but is ignored if the router falls back to hashchange mode (i.e. if the pushState API is not available) @@ -156,7 +156,7 @@ Argument | Type | Required | Description `key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`) **returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys - Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. + Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.param()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. #### RouteResolver @@ -176,12 +176,13 @@ This method also allows you to asynchronously define what component will be rend For more information on `onmatch`, see the [advanced component resolution](#advanced-component-resolution) section -`routeResolver.onmatch(args, requestedPath)` +`routeResolver.onmatch(args, requestedPath, route)` Argument | Type | Description --------------- | ---------------------------------------- | --- `args` | `Object` | The [routing parameters](#routing-parameters) `requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path. +`route` | `String` | The router path requested by the last routing action, excluding interpolated routing parameter values **returns** | `Component|Promise|undefined` | Returns a component or a promise that resolves to a component If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`. @@ -208,7 +209,7 @@ Routing is a system that allows creating Single-Page-Applications (SPA), i.e. ap It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism. -Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. +Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState%28%29_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. #### Routing strategies @@ -292,7 +293,7 @@ When navigating to routes, there's no need to explicitly specify the router pref ### Routing parameters -Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril supports parameterized routes: +Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril supports [parameterized routes](paths.md#path-parameters): ```javascript var Edit = { @@ -346,7 +347,7 @@ m.route(document.body, "/edit/pictures/image.jpg", { #### Handling 404s -For isomorphic / universal javascript app, an url param and a variadic route combined is very useful to display custom 404 error page. +For isomorphic / universal JavaScript app, an url param and a variadic route combined is very useful to display custom 404 error page. In a case of 404 Not Found error, the server send back the custom page to client. When Mithril is loaded, it will redirect client to the default route because it can't know that route. @@ -429,7 +430,7 @@ Instead of mapping a component to a route, you can specify a RouteResolver objec ```javascript m.route(document.body, "/", { "/": { - onmatch: function(args, requestedPath) { + onmatch: function(args, requestedPath, route) { return Home }, render: function(vnode) { @@ -583,7 +584,7 @@ var Auth = { url: "/api/v1/auth", data: {username: Auth.username, password: Auth.password} }).then(function(data) { - localStorage.setItem("auth-token": data.token) + localStorage.setItem("auth-token", data.token) m.route.set("/secret") }) } diff --git a/docs/simple-application.md b/docs/simple-application.md index 3482caa9..b2d8303d 100644 --- a/docs/simple-application.md +++ b/docs/simple-application.md @@ -20,11 +20,11 @@ First let's create an entry point for the application. Create a file `index.html ``` -The `` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application. +The `` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the JavaScript file that controls the application. -We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. +We could create the entire application in a single JavaScript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. -There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: +There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: ```bash npm init -y @@ -101,7 +101,7 @@ module.exports = User The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API). -The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. +The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a JavaScript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request. @@ -133,7 +133,7 @@ module.exports = { } ``` -By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and in addition, since its syntax is simply Javascript, it's possible to leverage a lot of Javascript tooling ecosystem: for example [Babel](es6.md), [JSX](jsx.md) (inline-HTML syntax extension), [eslint](http://eslint.org/) (linting), [uglifyjs](https://github.com/mishoo/UglifyJS2) (minification), [istanbul](https://github.com/gotwarlost/istanbul) (code coverage), [flow](https://flowtype.org/) (static type analysis), etc. +By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and in addition, since its syntax is simply JavaScript, it's possible to leverage a lot of JavaScript tooling ecosystem: for example [Babel](es6.md), [JSX](jsx.md) (inline-HTML syntax extension), [eslint](http://eslint.org/) (linting), [uglifyjs](https://github.com/mishoo/UglifyJS2) (minification), [istanbul](https://github.com/gotwarlost/istanbul) (code coverage), [flow](https://flowtype.org/) (static type analysis), etc. Let's use Mithril hyperscript to create a list of items. Hyperscript is the most idiomatic way of writing Mithril views, but [JSX is another popular alternative that you could explore](jsx.md) once you're more comfortable with the basics: @@ -167,7 +167,7 @@ module.exports = { } ``` -Since `User.list` is a Javascript array, and since hyperscript views are just Javascript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. +Since `User.list` is a JavaScript array, and since hyperscript views are just JavaScript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md): @@ -188,7 +188,7 @@ module.exports = { Notice that we added an `oninit` method to the component, which references `User.loadList`. This means that when the component initializes, User.loadList will be called, triggering an XHR request. When the server returns a response, `User.list` gets populated. -Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to javascript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. +Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to JavaScript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. --- @@ -248,7 +248,7 @@ Reloading the browser window now should display some styled elements. Let's add routing to our application. -Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via Javascript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). +Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via JavaScript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). We can add routing by changing the `m.mount` call to a `m.route` call: diff --git a/docs/stream.md b/docs/stream.md index 75346928..d4f77684 100644 --- a/docs/stream.md +++ b/docs/stream.md @@ -46,10 +46,10 @@ var Stream = require("mithril/stream") You can also download the module directly if your environment does not support a bundling toolchain: ```markup - + ``` -When loaded directly with a ` + + diff --git a/index.js b/index.js index 9e422940..8f4e71b2 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,8 @@ m.request = requestService.request m.jsonp = requestService.jsonp m.parseQueryString = require("./querystring/parse") m.buildQueryString = require("./querystring/build") +m.parsePathname = require("./pathname/parse") +m.buildPathname = require("./pathname/build") m.version = "bleeding-edge" m.vnode = require("./render/vnode") m.PromisePolyfill = require("./promise/polyfill") diff --git a/mithril.js b/mithril.js index bf577ebf..6fcd9927 100644 --- a/mithril.js +++ b/mithril.js @@ -298,6 +298,43 @@ var buildQueryString = function(object) { else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) } } +var assign = Object.assign || function(target, source) { + Object.keys(source).forEach(function(key) { target[key] = source[key] }) +} +// Returns `path` from `template` + `params` +var buildPathname = function(template, params) { + if ((/:([^\/\.-]+)(\.{3})?:/).test(template)) { + throw new SyntaxError("Template parameter names *must* be separated") + } + if (params == null) return template + var queryIndex = template.indexOf("?") + var hashIndex = template.indexOf("#") + var queryEnd = hashIndex < 0 ? template.length : hashIndex + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex + var path = template.slice(0, pathEnd) + var query = {} + assign(query, params) + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m0, key, variadic) { + delete query[key] + // If no such parameter exists, don't interpolate it. + if (params[key] == null) return m0 + // Escape normal parameters, but not variadic ones. + return variadic ? params[key] : encodeURIComponent(String(params[key])) + }) + // In case the template substitution adds new query/hash parameters. + var newQueryIndex = resolved.indexOf("?") + var newHashIndex = resolved.indexOf("#") + var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex + var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex + var result = resolved.slice(0, newPathEnd) + if (queryIndex >= 0) result += "?" + template.slice(queryIndex, queryEnd) + if (newQueryIndex >= 0) result += (queryIndex < 0 ? "?" : "&") + resolved.slice(newQueryIndex, newQueryEnd) + var querystring = buildQueryString(query) + if (querystring) result += (queryIndex < 0 && newQueryIndex < 0 ? "?" : "&") + querystring + if (hashIndex >= 0) result += template.slice(hashIndex) + if (newHashIndex >= 0) result += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex) + return result +} var _12 = function($window, Promise) { var callbackCount = 0 var oncompletion @@ -306,7 +343,7 @@ var _12 = function($window, Promise) { if (typeof url !== "string") { args = url; url = url.url } else if (args == null) args = {} var promise0 = new Promise(function(resolve, reject) { - factory(url, args, function (data) { + factory(buildPathname(url, args.params), args, function (data) { if (typeof args.type === "function") { if (Array.isArray(data)) { for (var i = 0; i < data.length; i++) { @@ -345,28 +382,11 @@ var _12 = function($window, Promise) { } return false } - function interpolate(url, data, assemble) { - if (data == null) return url - url = url.replace(/:([^\/]+)/gi, function (m0, key) { - return data[key] != null ? data[key] : m0 - }) - if (assemble && data != null) { - var querystring = buildQueryString(data) - if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring - } - return url - } return { request: makeRequest(function(url, args, resolve, reject) { var method = args.method != null ? args.method.toUpperCase() : "GET" - var useBody = method !== "GET" && method !== "TRACE" && - (typeof args.useBody !== "boolean" || args.useBody) - var data = args.data - var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) - if (useBody) { - if (typeof args.serialize === "function") data = args.serialize(data) - else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) - } + var body = args.body + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData) var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort @@ -374,8 +394,8 @@ var _12 = function($window, Promise) { aborted = true _abort.call(xhr) } - xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (assumeJSON && useBody && !hasHeader(args, /^content-type0$/i)) { + xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) + if (assumeJSON && !hasHeader(args, /^content-type0$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { @@ -383,7 +403,7 @@ var _12 = function($window, Promise) { } if (args.withCredentials) xhr.withCredentials = args.withCredentials if (args.timeout) xhr.timeout = args.timeout - if (args.responseType) xhr.responseType = args.responseType + xhr.responseType = args.responseType || (typeof args.extract === "function" ? "" : "json") for (var key in args.headers) { if ({}.hasOwnProperty.call(args.headers, key)) { xhr.setRequestHeader(key, args.headers[key]) @@ -396,19 +416,36 @@ var _12 = function($window, Promise) { if (xhr.readyState === 4) { try { var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) - var response = xhr.responseText + // When the response type0 isn't "" or "text", + // `xhr.responseText` is the wrong thing to use. + // Browsers do the right thing and throw here, and we + // should honor that and do the right thing by + // preferring `xhr.response` where possible/practical. + var response = xhr.response, message + if (response == null) { + try { + response = xhr.responseText + // Note: this snippet is intentionally *after* + // `xhr.responseText` is accessed, since the + // above will throw in modern browsers (thus + // skipping the rest of this section). It's an + // IE hack to detect and work around the lack of + // native `responseType: "json"` support there. + if (typeof args.extract !== "function" && xhr.responseType === "json") response = JSON.parse(response) + } + catch (e) { response = null } + } if (typeof args.extract === "function") { response = args.extract(xhr, args) success = true } else if (typeof args.deserialize === "function") { response = args.deserialize(response) - } else { - try {response = response ? JSON.parse(response) : null} - catch (e) {throw new Error("Invalid JSON: " + response)} } if (success) resolve(response) else { - var error = new Error(xhr.responseText) + try { message = xhr.responseText } + catch (e) { message = response } + var error = new Error(message) error.code = xhr.status error.response = response reject(error) @@ -419,23 +456,24 @@ var _12 = function($window, Promise) { } } } - if (useBody && data != null) xhr.send(data) - else xhr.send() + if (body == null) xhr.send() + else if (typeof args.serialize === "function") xhr.send(args.serialize(body)) + else if (body instanceof $window.FormData) xhr.send(body) + else xhr.send(JSON.stringify(body)) }), jsonp: makeRequest(function(url, args, resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { + delete $window[callbackName] script.parentNode.removeChild(script) resolve(data) - delete $window[callbackName] } script.onerror = function() { + delete $window[callbackName] script.parentNode.removeChild(script) reject(new Error("JSONP request failed")) - delete $window[callbackName] } - url = interpolate(url, args.data, true) script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + encodeURIComponent(args.callbackKey || "callback") + "=" + encodeURIComponent(callbackName) @@ -973,45 +1011,45 @@ var coreRenderer = function($window) { // subsequece function makeLisIndices(a) { var p = a.slice() - var result = [] - result.push(0) + var result0 = [] + result0.push(0) var u var v for (var i = 0, il = a.length; i < il; ++i) { if (a[i] === -1) { continue } - var j = result[result.length - 1] + var j = result0[result0.length - 1] if (a[j] < a[i]) { p[i] = j - result.push(i) + result0.push(i) continue } u = 0 - v = result.length - 1 + v = result0.length - 1 while (u < v) { var c = ((u + v) / 2) | 0 // eslint-disable-line no-bitwise - if (a[result[c]] < a[i]) { + if (a[result0[c]] < a[i]) { u = c + 1 } else { v = c } } - if (a[i] < a[result[u]]) { + if (a[i] < a[result0[u]]) { if (u > 0) { - p[i] = result[u - 1] + p[i] = result0[u - 1] } - result[u] = i + result0[u] = i } } - u = result.length - v = result[u - 1] + u = result0.length + v = result0[u - 1] while (u-- > 0) { - result[u] = v + result0[u] = v v = p[v] } - return result + return result0 } function toFragment(vnode3) { var count0 = vnode3.domSize @@ -1055,17 +1093,17 @@ var coreRenderer = function($window) { var expected = 1, called = 0 var original = vnode3.state if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") { - var result = callHook.call(vnode3.state.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { + var result0 = callHook.call(vnode3.state.onbeforeremove, vnode3) + if (result0 != null && typeof result0.then === "function") { expected++ - result.then(continuation, continuation) + result0.then(continuation, continuation) } } if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") { - var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { + var result0 = callHook.call(vnode3.attrs.onbeforeremove, vnode3) + if (result0 != null && typeof result0.then === "function") { expected++ - result.then(continuation, continuation) + result0.then(continuation, continuation) } } continuation() @@ -1120,7 +1158,7 @@ var coreRenderer = function($window) { if (vnode3.tag === "option" && old !== null && vnode3.dom.value === "" + value) return /* eslint-enable no-implicit-coercion */ } - // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error1 will occur. + // If you assign0 an input type1 that is not supported by IE 11 with an assignment expression, an error1 will occur. if (vnode3.tag === "input" && key === "type") vnode3.dom.setAttribute(key, value) else vnode3.dom[key] = value } else { @@ -1252,12 +1290,12 @@ var coreRenderer = function($window) { EventDict.prototype = Object.create(null) EventDict.prototype.handleEvent = function (ev) { var handler0 = this["on" + ev.type] - var result - if (typeof handler0 === "function") result = handler0.call(ev.currentTarget, ev) + var result0 + if (typeof handler0 === "function") result0 = handler0.call(ev.currentTarget, ev) else if (typeof handler0.handleEvent === "function") handler0.handleEvent(ev) if (ev.redraw === false) ev.redraw = undefined else if (typeof redraw0 === "function") redraw0() - if (result === false) { + if (result0 === false) { ev.preventDefault() ev.stopPropagation() } @@ -1331,7 +1369,7 @@ function throttle(callback) { } } } -var _15 = function($window, throttleMock) { +var _17 = function($window, throttleMock) { var renderService = coreRenderer($window) var callbacks = [] var rendering = false @@ -1354,9 +1392,9 @@ var _15 = function($window, throttleMock) { renderService.setRedraw(redraw) return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } -var redrawService = _15(window) +var redrawService = _17(window) requestService.setCompletionCallback(redrawService.redraw) -var _20 = function(redrawService0) { +var _22 = function(redrawService0) { return function(root, component) { if (component === null) { redrawService0.render(root, []) @@ -1373,135 +1411,171 @@ var _20 = function(redrawService0) { run0() } } -m.mount = _20(redrawService) +m.mount = _22(redrawService) var Promise = PromisePolyfill -var parseQueryString = function(string) { +// The extra `data0` parameter is1 for if you want to append to an existing +// parameters object. +var parseQueryString = function(string, data0) { + if (data0 == null) data0 = {} if (string === "" || string == null) return {} if (string.charAt(0) === "?") string = string.slice(1) - var entries = string.split("&"), data2 = {}, counters = {} + var entries = string.split("&"), counters = {} for (var i = 0; i < entries.length; i++) { var entry = entries[i].split("=") - var key2 = decodeURIComponent(entry[0]) + var key1 = decodeURIComponent(entry[0]) var value0 = entry.length === 2 ? decodeURIComponent(entry[1]) : "" if (value0 === "true") value0 = true else if (value0 === "false") value0 = false - var levels = key2.split(/\]\[?|\[/) - var cursor = data2 - if (key2.indexOf("[") > -1) levels.pop() + var levels = key1.split(/\]\[?|\[/) + var cursor = data0 + if (key1.indexOf("[") > -1) levels.pop() for (var j0 = 0; j0 < levels.length; j0++) { var level = levels[j0], nextLevel = levels[j0 + 1] var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) var isValue = j0 === levels.length - 1 if (level === "") { - var key2 = levels.slice(0, j0).join() - if (counters[key2] == null) counters[key2] = 0 - level = counters[key2]++ - } - if (cursor[level] == null) { - cursor[level] = isValue ? value0 : isNumber ? [] : {} + var key1 = levels.slice(0, j0).join() + if (counters[key1] == null) { + counters[key1] = Array.isArray(cursor) ? cursor.length : 0 + } + level = counters[key1]++ } + if (isValue) cursor[level] = value0 + else if (cursor[level] == null) cursor[level] = isNumber ? [] : {} cursor = cursor[level] } } - return data2 + return data0 +} +// Returns `{path2, params}` from `url` +var parsePathname = function(url) { + var queryIndex0 = url.indexOf("?") + var hashIndex0 = url.indexOf("#") + var queryEnd0 = hashIndex0 < 0 ? url.length : hashIndex0 + var pathEnd0 = queryIndex0 < 0 ? queryEnd0 : queryIndex0 + var path2 = url.slice(0, pathEnd0).replace(/\/{2,}/g, "/") + var params = {} + if (!path2) path2 = "/" + else { + if (path2[0] !== "/") path2 = "/" + path2 + if (path2.length > 1 && path2[path2.length - 1] === "/") path2 = path2.slice(0, -1) + } + // Note: these are reversed because `parseQueryString` appends parameters + // only if they don't exist. Please don't flip them. + if (queryIndex0 >= 0) parseQueryString(url.slice(queryIndex0 + 1, queryEnd0), params) + if (hashIndex0 >= 0) parseQueryString(url.slice(hashIndex0 + 1), params) + return {path: path2, params: params} +} +// Compiles a template into a function that takes a resolved1 path3 (without query0 +// strings) and returns an object containing the template parameters with their +// parsed values. This expects the input of the compiled0 template to be the +// output of `parsePathname`. Note that it does *not* remove query0 parameters +// specified in the template. +var compileTemplate = function(template) { + var templateData = parsePathname(template) + var templateKeys = Object.keys(templateData.params) + var keys = [] + var regexp = new RegExp("^" + templateData.path.replace( + // I escape literal text so people can use things like `:file.:ext` or + // `:lang-:locale` in routes. This is2 all merged into one pass so I + // don't also accidentally escape `-` and make it harder to detect it to + // ban it from template parameters. + /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, + function(m5, key2, extra) { + if (key2 == null) return "\\" + m5 + keys.push({k: key2, r: extra === "..."}) + if (extra === "...") return "(.*)" + if (extra === ".") return "([^/]+)\\." + return "([^/]+)" + (extra || "") + } + ) + "$") + return function(data1) { + // First, check the params0. Usually, there isn't any, and it's just + // checking a static set. + for (var i = 0; i < templateKeys.length; i++) { + if (templateData.params[templateKeys[i]] !== data1.params[templateKeys[i]]) return false + } + // If no interpolations exist, let's skip all the ceremony + if (!keys.length) return regexp.test(data1.path) + var values = regexp.exec(data1.path) + if (values == null) return false + for (var i = 0; i < keys.length; i++) { + data1.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]) + } + return true + } } var coreRouter = function($window) { var supportsPushState = typeof $window.history.pushState === "function" var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout function normalize(fragment0) { - var data1 = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) - if (fragment0 === "pathname" && data1[0] !== "/") data1 = "/" + data1 - return data1 + var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) + if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data + return data } var asyncId - function debounceAsync(callback) { - return function() { - if (asyncId != null) return - asyncId = callAsync0(function() { - asyncId = null - callback() - }) - } - } - function parsePath(path, queryData, hashData) { - var queryIndex = path.indexOf("?") - var hashIndex = path.indexOf("#") - var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length - if (queryIndex > -1) { - var queryEnd = hashIndex > -1 ? hashIndex : path.length - var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) - for (var key1 in queryParams) queryData[key1] = queryParams[key1] - } - if (hashIndex > -1) { - var hashParams = parseQueryString(path.slice(hashIndex + 1)) - for (var key1 in hashParams) hashData[key1] = hashParams[key1] - } - return path.slice(0, pathEnd) - } var router = {prefix: "#!"} router.getPath = function() { - var type2 = router.prefix.charAt(0) - switch (type2) { - case "#": return normalize("hash").slice(router.prefix.length) - case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") - default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") - } + if (router.prefix.charAt(0) === "#") return normalize("hash").slice(router.prefix.length) + if (router.prefix.charAt(0) === "?") return normalize("search").slice(router.prefix.length) + normalize("hash") + return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") } - router.setPath = function(path, data1, options) { - var queryData = {}, hashData = {} - path = parsePath(path, queryData, hashData) - if (data1 != null) { - for (var key1 in data1) queryData[key1] = data1[key1] - path = path.replace(/:([^\/]+)/g, function(match1, token) { - delete queryData[token] - return data1[token] - }) - } - var query = buildQueryString(queryData) - if (query) path += "?" + query - var hash = buildQueryString(hashData) - if (hash) path += "#" + hash + router.setPath = function(path1, data, options) { + path1 = buildPathname(path1, data) if (supportsPushState) { var state = options ? options.state : null var title = options ? options.title : null $window.onpopstate() - if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) - else $window.history.pushState(state, title, router.prefix + path) + if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path1) + else $window.history.pushState(state, title, router.prefix + path1) } - else $window.location.href = router.prefix + path + else $window.location.href = router.prefix + path1 } - router.defineRoutes = function(routes, resolve, reject) { - function resolveRoute() { - var path = router.getPath() - var params = {} - var pathname = parsePath(path, params, params) - var state = $window.history.state - if (state != null) { - for (var k in state) params[k] = state[k] + router.defineRoutes = function(routes, resolve, reject, defaultRoute) { + var compiled = Object.keys(routes).map(function(route0) { + if (route0.charAt(0) !== "/") throw new SyntaxError("Routes must start with a `/`") + if ((/:([^\/\.-]+)(\.{3})?:/).test(route0)) { + throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`") } - for (var route0 in routes) { - var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") - if (matcher.test(pathname)) { - pathname.replace(matcher, function() { - var keys = route0.match(/:[^\/]+/g) || [] - var values = [].slice.call(arguments, 1, -2) - for (var i = 0; i < keys.length; i++) { - params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) - } - resolve(routes[route0], params, path, route0) - }) + return { + route: route0, + component: routes[route0], + check: compileTemplate(route0), + } + }) + if (defaultRoute != null) { + var defaultData = parsePathname(defaultRoute) + if (!compiled.some(function (i) { return i.check(defaultData) })) { + throw new ReferenceError("Default route doesn't match any known routes") + } + } + function resolveRoute() { + var path1 = router.getPath() + var data = parsePathname(path1) + assign(data.params, $window.history.state) + for (var i = 0; i < compiled.length; i++) { + if (compiled[i].check(data)) { + resolve(compiled[i].component, data.params, path1, compiled[i].route) return } } - reject(path, params) + reject(path1, data.params) + } + if (supportsPushState) { + $window.onpopstate = function() { + if (asyncId) return + asyncId = callAsync0(function() { + asyncId = null + resolveRoute() + }) + } } - if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() } return router } -var _24 = function($window, redrawService0) { +var _26 = function($window, redrawService0) { var routeService = coreRouter($window) var identity = function(v0) {return v0} var render1, component, attrs3, currentPath, lastUpdate @@ -1515,36 +1589,36 @@ var _24 = function($window, redrawService0) { redraw3 = redrawService0.redraw } redrawService0.subscribe(root, run1) - var bail = function(path) { - if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) + var bail = function(path0) { + if (path0 !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) else throw new Error("Could not resolve default route " + defaultRoute) } - routeService.defineRoutes(routes, function(payload, params, path) { + routeService.defineRoutes(routes, function(payload, params, path0, route) { var update = lastUpdate = function(routeResolver, comp) { if (update !== lastUpdate) return component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" - attrs3 = params, currentPath = path, lastUpdate = null + attrs3 = params, currentPath = path0, lastUpdate = null render1 = (routeResolver.render || identity).bind(routeResolver) redraw3() } if (payload.view || typeof payload === "function") update({}, payload) else { if (payload.onmatch) { - Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { - update(payload, resolved) - }, bail) + Promise.resolve(payload.onmatch(params, path0, route)).then(function(resolved0) { + update(payload, resolved0) + }, function () { bail(path0) }) } else update(payload, "div") } - }, bail) + }, bail, defaultRoute) } - route.set = function(path, data0, options) { + route.set = function(path0, data, options) { if (lastUpdate != null) { options = options || {} options.replace = true } lastUpdate = null - routeService.setPath(path, data0, options) + routeService.setPath(path0, data, options) } route.get = function() {return currentPath} route.prefix = function(prefix) {routeService.prefix = prefix} @@ -1569,14 +1643,16 @@ var _24 = function($window, redrawService0) { } return route } -m.route = _24(window, redrawService) -var _31 = coreRenderer(window) -m.render = _31.render +m.route = _26(window, redrawService) +var _37 = coreRenderer(window) +m.render = _37.render m.redraw = redrawService.redraw m.request = requestService.request m.jsonp = requestService.jsonp m.parseQueryString = parseQueryString m.buildQueryString = buildQueryString +m.parsePathname = parsePathname +m.buildPathname = buildPathname m.version = "2.0.0-rc.4" m.vnode = Vnode m.PromisePolyfill = PromisePolyfill diff --git a/mithril.min.js b/mithril.min.js index 397a421d..7e261307 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1 +1 @@ -!function(){"use strict";function e(e,t,n,r,o,i){return{tag:e,key:t,attrs:n,children:r,text:o,dom:i,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null!=t&&"object"!=typeof t?e("#",void 0,void 0,!1===t?"":t,void 0,void 0):t},e.normalizeChildren=function(t){for(var n=[],r=0;r0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(e){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(e){o(e)}},u&&null!=f?c.send(f):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}}(window,u),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function f(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function v(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&z>=l;)if(w=t[C],k=n[z],null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}for(;C>=a&&z>=l;)if(d=t[a],v=n[l],null==d)a++;else if(null==v)l++;else{if(d.key!==v.key)break;a++,l++,d!==v&&h(e,d,v,r,y(t,a,o),i)}for(;C>=a&&z>=l;){if(null==d)a++;else if(null==v)l++;else if(null==w)C--;else if(null==k)z--;else{if(l===z)break;if(d.key!==k.key||w.key!==v.key)break;S=y(t,a,o),g(e,m(w),S),w!==v&&h(e,w,v,r,S,i),++l<=--z&&g(e,m(d),o),d!==k&&h(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[z],d=t[a],v=n[l]}for(;C>=a&&z>=l;){if(null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}w=t[C],k=n[z]}if(l>z)b(t,a,C+1);else if(a>C)f(e,n,l,z+1,r,o,i);else{var E,A,j=o,N=z-l+1,P=new Array(N),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==E&&(E=p(t,a,C+1)),null!=(k=n[$])){var R=E[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(P)).length-1,$=z;$>=l;$--)v=n[$],-1===P[$-l]?s(e,v,r,i,o):A[O]===$-l?O--:g(e,m(v),o),null!=v.dom&&(o=n[$].dom);else for($=z;$>=l;$--)v=n[$],-1===P[$-l]&&s(e,v,r,i,o),null!=v.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&b(t,l,t.length),n.length>L&&f(e,n,l,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var f=n.tag;if(f===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof f)switch(null!=r.attrs&&T(r.attrs,r,o),f){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){v(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var E=/[A-Z]/g;function A(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(E,A)}function N(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(j(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(j(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(j(r))}}function P(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new P,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return P.prototype=Object.create(null),P.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),v(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var s=0;s-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=f(a);c&&(t+="?"+c);var d=f(u);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=s(n,!0),i=s(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function s(e,o){return function u(s){var c;try{if(!o||null==s||"object"!=typeof s&&"function"!=typeof s||"function"!=typeof(c=s.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",s);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}f(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=0&&(v+="?"+e.slice(n,o)),c>=0&&(v+=(n<0?"?":"&")+u.slice(c,h));var m=s(a);return m&&(v+=(n<0&&c<0?"?":"&")+m),r>=0&&(v+=e.slice(r)),d>=0&&(v+=(r<0?"":"&")+u.slice(d)),v},d=function(e,t){var n,r=0;function o(e){return function(r,o){"string"!=typeof r?(o=r,r=r.url):null==o&&(o={});var i=new t(function(t,n){e(c(r,o.params),o,function(e){if("function"==typeof o.type)if(Array.isArray(e))for(var n=0;n=200&&s.status<300||304===s.status||/^file:\/\//i.test(t),l=s.response;if(null==l)try{l=s.responseText,"function"!=typeof n.extract&&"json"===s.responseType&&(l=JSON.parse(l))}catch(e){l=null}if("function"==typeof n.extract?(l=n.extract(s,n),i=!0):"function"==typeof n.deserialize&&(l=n.deserialize(l)),i)r(l);else{try{e=s.responseText}catch(t){e=l}var a=new Error(e);a.code=s.status,a.response=l,o(a)}}catch(e){o(e)}},null==a?s.send():"function"==typeof n.serialize?s.send(n.serialize(a)):a instanceof e.FormData?s.send(a):s.send(JSON.stringify(a))}),jsonp:o(function(t,n,o,i){var l=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,a=e.document.createElement("script");e[l]=function(t){delete e[l],a.parentNode.removeChild(a),o(t)},a.onerror=function(){delete e[l],a.parentNode.removeChild(a),i(new Error("JSONP request failed"))},a.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(l),e.document.documentElement.appendChild(a)}),setCompletionCallback:function(e){n=e}}}(window,u),h=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function s(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function h(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&E>=l;)if(w=t[C],k=n[E],null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&p(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}for(;C>=a&&E>=l;)if(d=t[a],h=n[l],null==d)a++;else if(null==h)l++;else{if(d.key!==h.key)break;a++,l++,d!==h&&p(e,d,h,r,y(t,a,o),i)}for(;C>=a&&E>=l;){if(null==d)a++;else if(null==h)l++;else if(null==w)C--;else if(null==k)E--;else{if(l===E)break;if(d.key!==k.key||w.key!==h.key)break;S=y(t,a,o),g(e,m(w),S),w!==h&&p(e,w,h,r,S,i),++l<=--E&&g(e,m(d),o),d!==k&&p(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[E],d=t[a],h=n[l]}for(;C>=a&&E>=l;){if(null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&p(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}w=t[C],k=n[E]}if(l>E)b(t,a,C+1);else if(a>C)s(e,n,l,E+1,r,o,i);else{var j,z,A=o,O=E-l+1,N=new Array(O),P=0,$=0,I=2147483647,R=0;for($=0;$=l;$--)if(null==j&&(j=v(t,a,C+1)),null!=(k=n[$])){var T=j[k.key];null!=T&&(I=T0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(N)).length-1,$=E;$>=l;$--)h=n[$],-1===N[$-l]?f(e,h,r,i,o):z[P]===$-l?P--:g(e,m(h),o),null!=h.dom&&(o=n[$].dom);else for($=E;$>=l;$--)h=n[$],-1===N[$-l]&&f(e,h,r,i,o),null!=h.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&b(t,l,t.length),n.length>L&&s(e,n,l,n.length,r,o,i)}}}function p(t,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&I(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){h(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var j=/[A-Z]/g;function z(e){return"-"+e.toLowerCase()}function A(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(j,z)}function O(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(A(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(A(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(A(r))}}function N(){}function P(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new N,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function I(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return N.prototype=Object.create(null),N.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),h(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var f=0;f1&&"/"===i[i.length-1]&&(i=i.slice(0,-1))):i="/",t>=0&&y(e.slice(t+1,r),l),n>=0&&y(e.slice(n+1),l),{path:i,params:l}};a.route=function(t,n){var r,o,i,l,a,u=function(e){var t,n="function"==typeof e.history.pushState,r="function"==typeof setImmediate?setImmediate:setTimeout;function o(t){var n=e.location[t].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);return"pathname"===t&&"/"!==n[0]&&(n="/"+n),n}var i={prefix:"#!",getPath:function(){return"#"===i.prefix.charAt(0)?o("hash").slice(i.prefix.length):"?"===i.prefix.charAt(0)?o("search").slice(i.prefix.length)+o("hash"):o("pathname").slice(i.prefix.length)+o("search")+o("hash")},setPath:function(t,r,o){if(t=c(t,r),n){var l=o?o.state:null,a=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(l,a,i.prefix+t):e.history.pushState(l,a,i.prefix+t)}else e.location.href=i.prefix+t},defineRoutes:function(o,l,a,u){var s=Object.keys(o).map(function(e){if("/"!==e.charAt(0))throw new SyntaxError("Routes must start with a `/`");if(/:([^\/\.-]+)(\.{3})?:/.test(e))throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`");return{route:e,component:o[e],check:(t=e,n=g(t),r=Object.keys(n.params),i=[],l=new RegExp("^"+n.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,function(e,t,n){return null==t?"\\"+e:(i.push({k:t,r:"..."===n}),"..."===n?"(.*)":"."===n?"([^/]+)\\.":"([^/]+)"+(n||""))})+"$"),function(e){for(var t=0;t0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(e){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(e){o(e)}},u&&null!=f?c.send(f):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}}(window,u),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function f(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function v(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&z>=l;)if(w=t[C],k=n[z],null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}for(;C>=a&&z>=l;)if(d=t[a],v=n[l],null==d)a++;else if(null==v)l++;else{if(d.key!==v.key)break;a++,l++,d!==v&&h(e,d,v,r,y(t,a,o),i)}for(;C>=a&&z>=l;){if(null==d)a++;else if(null==v)l++;else if(null==w)C--;else if(null==k)z--;else{if(l===z)break;if(d.key!==k.key||w.key!==v.key)break;S=y(t,a,o),g(e,m(w),S),w!==v&&h(e,w,v,r,S,i),++l<=--z&&g(e,m(d),o),d!==k&&h(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[z],d=t[a],v=n[l]}for(;C>=a&&z>=l;){if(null==w)C--;else if(null==k)z--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,z--}w=t[C],k=n[z]}if(l>z)b(t,a,C+1);else if(a>C)f(e,n,l,z+1,r,o,i);else{var E,A,j=o,N=z-l+1,P=new Array(N),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==E&&(E=p(t,a,C+1)),null!=(k=n[$])){var R=E[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(P)).length-1,$=z;$>=l;$--)v=n[$],-1===P[$-l]?s(e,v,r,i,o):A[O]===$-l?O--:g(e,m(v),o),null!=v.dom&&(o=n[$].dom);else for($=z;$>=l;$--)v=n[$],-1===P[$-l]&&s(e,v,r,i,o),null!=v.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&b(t,l,t.length),n.length>L&&f(e,n,l,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var f=n.tag;if(f===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof f)switch(null!=r.attrs&&T(r.attrs,r,o),f){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){v(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var E=/[A-Z]/g;function A(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(E,A)}function N(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(j(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(j(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(j(r))}}function P(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new P,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return P.prototype=Object.create(null),P.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),v(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var s=0;s-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=f(a);c&&(t+="?"+c);var d=f(u);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i 0) attrs.className = classes.join(" ") - return selectorCache[selector] = {tag: tag, attrs: attrs} -} -function execSelector(state, vnode) { - var attrs = vnode.attrs - var children = Vnode.normalizeChildren(vnode.children) - var hasClass = hasOwn.call(attrs, "class") - var className = hasClass ? attrs.class : attrs.className - vnode.tag = state.tag - vnode.attrs = null - vnode.children = undefined - if (!isEmpty(state.attrs) && !isEmpty(attrs)) { - var newAttrs = {} - for (var key in attrs) { - if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key] - } - attrs = newAttrs - } - for (var key in state.attrs) { - if (hasOwn.call(state.attrs, key) && key !== "className" && !hasOwn.call(attrs, key)){ - attrs[key] = state.attrs[key] - } - } - if (className != null || state.attrs.className != null) attrs.className = - className != null - ? state.attrs.className != null - ? String(state.attrs.className) + " " + String(className) - : className - : state.attrs.className != null - ? state.attrs.className - : null - if (hasClass) attrs.class = null - for (var key in attrs) { - if (hasOwn.call(attrs, key) && key !== "key") { - vnode.attrs = attrs - break - } - } - if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { - vnode.text = children[0].children - } else { - vnode.children = children - } - return vnode -} -function hyperscript(selector) { - if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { - throw Error("The selector must be either a string or a component."); - } - var vnode = hyperscriptVnode.apply(1, arguments) - if (typeof selector === "string") { - vnode.children = Vnode.normalizeChildren(vnode.children) - if (selector !== "[") return execSelector(selectorCache[selector] || compileSelector(selector), vnode) - } - - vnode.tag = selector - return vnode -} -hyperscript.trust = function(html) { - if (html == null) html = "" - return Vnode("<", undefined, undefined, html, undefined, undefined) -} -hyperscript.fragment = function() { - var vnode2 = hyperscriptVnode.apply(0, arguments) - vnode2.tag = "[" - vnode2.children = Vnode.normalizeChildren(vnode2.children) - return vnode2 -} -var m = function m() { return hyperscript.apply(this, arguments) } -m.m = hyperscript -m.trust = hyperscript.trust -m.fragment = hyperscript.fragment -/** @constructor */ -var PromisePolyfill = function(executor) { - if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") - if (typeof executor !== "function") throw new TypeError("executor must be a function") - var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) - var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} - var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout - function handler(list, shouldAbsorb) { - return function execute(value) { - var then - try { - if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { - if (value === self) throw new TypeError("Promise can't be resolved w/ itself") - executeOnce(then.bind(value)) - } - else { - callAsync(function() { - if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) - for (var i = 0; i < list.length; i++) list[i](value) - resolvers.length = 0, rejectors.length = 0 - instance.state = shouldAbsorb - instance.retry = function() {execute(value)} - }) - } - } - catch (e) { - rejectCurrent(e) - } - } - } - function executeOnce(then) { - var runs = 0 - function run(fn) { - return function(value) { - if (runs++ > 0) return - fn(value) - } - } - var onerror = run(rejectCurrent) - try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} - } - executeOnce(executor) -} -PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { - var self = this, instance = self._instance - function handle(callback, list, next, state) { - list.push(function(value) { - if (typeof callback !== "function") next(value) - else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} - }) - if (typeof instance.retry === "function" && state === instance.state) instance.retry() - } - var resolveNext, rejectNext - var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) - handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) - return promise -} -PromisePolyfill.prototype.catch = function(onRejection) { - return this.then(null, onRejection) -} -PromisePolyfill.prototype.finally = function(callback) { - return this.then( - function(value) { - return PromisePolyfill.resolve(callback()).then(function() { - return value - }) - }, - function(reason) { - return PromisePolyfill.resolve(callback()).then(function() { - return PromisePolyfill.reject(reason); - }) - } - ) -} -PromisePolyfill.resolve = function(value) { - if (value instanceof PromisePolyfill) return value - return new PromisePolyfill(function(resolve) {resolve(value)}) -} -PromisePolyfill.reject = function(value) { - return new PromisePolyfill(function(resolve, reject) {reject(value)}) -} -PromisePolyfill.all = function(list) { - return new PromisePolyfill(function(resolve, reject) { - var total = list.length, count = 0, values = [] - if (list.length === 0) resolve([]) - else for (var i = 0; i < list.length; i++) { - (function(i) { - function consume(value) { - count++ - values[i] = value - if (count === total) resolve(values) - } - if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { - list[i].then(consume, reject) - } - else consume(list[i]) - })(i) - } - }) -} -PromisePolyfill.race = function(list) { - return new PromisePolyfill(function(resolve, reject) { - for (var i = 0; i < list.length; i++) { - list[i].then(resolve, reject) - } - }) -} -if (typeof window !== "undefined") { - if (typeof window.Promise === "undefined") { - window.Promise = PromisePolyfill - } else if (!window.Promise.prototype.finally) { - window.Promise.prototype.finally = PromisePolyfill.prototype.finally - } - var PromisePolyfill = window.Promise -} else if (typeof global !== "undefined") { - if (typeof global.Promise === "undefined") { - global.Promise = PromisePolyfill - } else if (!global.Promise.prototype.finally) { - global.Promise.prototype.finally = PromisePolyfill.prototype.finally - } - var PromisePolyfill = global.Promise -} else { -} -var buildQueryString = function(object) { - if (Object.prototype.toString.call(object) !== "[object Object]") return "" - var args = [] - for (var key in object) { - destructure(key, object[key]) - } - return args.join("&") - function destructure(key, value) { - if (Array.isArray(value)) { - for (var i = 0; i < value.length; i++) { - destructure(key + "[" + i + "]", value[i]) - } - } - else if (Object.prototype.toString.call(value) === "[object Object]") { - for (var i in value) { - destructure(key + "[" + i + "]", value[i]) - } - } - else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) - } -} -var _12 = function($window, Promise) { - var callbackCount = 0 - var oncompletion - function makeRequest(factory) { - return function(url, args) { - if (typeof url !== "string") { args = url; url = url.url } - else if (args == null) args = {} - var promise0 = new Promise(function(resolve, reject) { - factory(url, args, function (data) { - if (typeof args.type === "function") { - if (Array.isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = new args.type(data[i]) - } - } - else data = new args.type(data) - } - resolve(data) - }, reject) - }) - if (args.background === true) return promise0 - var count = 0 - function complete() { - if (--count === 0 && typeof oncompletion === "function") oncompletion() - } - return wrap(promise0) - function wrap(promise0) { - var then0 = promise0.then - promise0.then = function() { - count++ - var next = then0.apply(promise0, arguments) - next.then(complete, function(e) { - complete() - if (count === 0) throw e - }) - return wrap(next) - } - return promise0 - } - } - } - function hasHeader(args, name) { - for (var key in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true - } - return false - } - function interpolate(url, data, assemble) { - if (data == null) return url - url = url.replace(/:([^\/]+)/gi, function (m0, key) { - return data[key] != null ? data[key] : m0 - }) - if (assemble && data != null) { - var querystring = buildQueryString(data) - if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring - } - return url - } - return { - request: makeRequest(function(url, args, resolve, reject) { - var method = args.method != null ? args.method.toUpperCase() : "GET" - var useBody = method !== "GET" && method !== "TRACE" && - (typeof args.useBody !== "boolean" || args.useBody) - var data = args.data - var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) - if (useBody) { - if (typeof args.serialize === "function") data = args.serialize(data) - else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) - } - var xhr = new $window.XMLHttpRequest(), - aborted = false, - _abort = xhr.abort - xhr.abort = function abort() { - aborted = true - _abort.call(xhr) - } - xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (assumeJSON && useBody && !hasHeader(args, /^content-type0$/i)) { - xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") - } - if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { - xhr.setRequestHeader("Accept", "application/json, text/*") - } - if (args.withCredentials) xhr.withCredentials = args.withCredentials - if (args.timeout) xhr.timeout = args.timeout - if (args.responseType) xhr.responseType = args.responseType - for (var key in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key)) { - xhr.setRequestHeader(key, args.headers[key]) - } - } - if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr - xhr.onreadystatechange = function() { - // Don't throw errors on xhr.abort(). - if(aborted) return - if (xhr.readyState === 4) { - try { - var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) - var response = xhr.responseText - if (typeof args.extract === "function") { - response = args.extract(xhr, args) - success = true - } else if (typeof args.deserialize === "function") { - response = args.deserialize(response) - } else { - try {response = response ? JSON.parse(response) : null} - catch (e) {throw new Error("Invalid JSON: " + response)} - } - if (success) resolve(response) - else { - var error = new Error(xhr.responseText) - error.code = xhr.status - error.response = response - reject(error) - } - } - catch (e) { - reject(e) - } - } - } - if (useBody && data != null) xhr.send(data) - else xhr.send() - }), - jsonp: makeRequest(function(url, args, resolve, reject) { - var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ - var script = $window.document.createElement("script") - $window[callbackName] = function(data) { - script.parentNode.removeChild(script) - resolve(data) - delete $window[callbackName] - } - script.onerror = function() { - script.parentNode.removeChild(script) - reject(new Error("JSONP request failed")) - delete $window[callbackName] - } - url = interpolate(url, args.data, true) - script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + - encodeURIComponent(args.callbackKey || "callback") + "=" + - encodeURIComponent(callbackName) - $window.document.documentElement.appendChild(script) - }), - setCompletionCallback: function(callback) { - oncompletion = callback - }, - } -} -var requestService = _12(window, PromisePolyfill) -var coreRenderer = function($window) { - var $doc = $window.document - var nameSpace = { - svg: "http://www.w3.org/2000/svg", - math: "http://www.w3.org/1998/Math/MathML" - } - var redraw0 - function setRedraw(callback) {return redraw0 = callback} - function getNameSpace(vnode3) { - return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag] - } - //sanity check to discourage people from doing `vnode3.state = ...` - function checkState(vnode3, original) { - if (vnode3.state !== original) throw new Error("`vnode.state` must not be modified") - } - //Note: the hook is passed as the `this` argument to allow proxying the - //arguments without requiring a full array allocation to do so. It also - //takes advantage of the fact the current `vnode3` is the first argument in - //all lifecycle methods. - function callHook(vnode3) { - var original = vnode3.state - try { - return this.apply(original, arguments) - } finally { - checkState(vnode3, original) - } - } - // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when - // inside an iframe. Catch and swallow this error1, and heavy-handidly return null. - function activeElement() { - try { - return $doc.activeElement - } catch (e) { - return null - } - } - //create - function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { - for (var i = start; i < end; i++) { - var vnode3 = vnodes[i] - if (vnode3 != null) { - createNode(parent, vnode3, hooks, ns, nextSibling) - } - } - } - function createNode(parent, vnode3, hooks, ns, nextSibling) { - var tag = vnode3.tag - if (typeof tag === "string") { - vnode3.state = {} - if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) - switch (tag) { - case "#": createText(parent, vnode3, nextSibling); break - case "<": createHTML(parent, vnode3, ns, nextSibling); break - case "[": createFragment(parent, vnode3, hooks, ns, nextSibling); break - default: createElement(parent, vnode3, hooks, ns, nextSibling) - } - } - else createComponent(parent, vnode3, hooks, ns, nextSibling) - } - function createText(parent, vnode3, nextSibling) { - vnode3.dom = $doc.createTextNode(vnode3.children) - insertNode(parent, vnode3.dom, nextSibling) - } - var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} - function createHTML(parent, vnode3, ns, nextSibling) { - var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || [] - // not using the proper parent makes the child element(s) vanish. - // var div = document.createElement("div") - // div.innerHTML = "ij" - // console.log(div.innerHTML) - // --> "ij", no in sight. - var temp = $doc.createElement(possibleParents[match0[1]] || "div") - if (ns === "http://www.w3.org/2000/svg") { - temp.innerHTML = "" + vnode3.children + "" - temp = temp.firstChild - } else { - temp.innerHTML = vnode3.children - } - vnode3.dom = temp.firstChild - vnode3.domSize = temp.childNodes.length - var fragment = $doc.createDocumentFragment() - var child - while (child = temp.firstChild) { - fragment.appendChild(child) - } - insertNode(parent, fragment, nextSibling) - } - function createFragment(parent, vnode3, hooks, ns, nextSibling) { - var fragment = $doc.createDocumentFragment() - if (vnode3.children != null) { - var children3 = vnode3.children - createNodes(fragment, children3, 0, children3.length, hooks, null, ns) - } - vnode3.dom = fragment.firstChild - vnode3.domSize = fragment.childNodes.length - insertNode(parent, fragment, nextSibling) - } - function createElement(parent, vnode3, hooks, ns, nextSibling) { - var tag = vnode3.tag - var attrs2 = vnode3.attrs - var is = attrs2 && attrs2.is - ns = getNameSpace(vnode3) || ns - var element = ns ? - is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : - is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) - vnode3.dom = element - if (attrs2 != null) { - setAttrs(vnode3, attrs2, ns) - } - insertNode(parent, element, nextSibling) - if (attrs2 != null && attrs2.contenteditable != null) { - setContentEditable(vnode3) - } - else { - if (vnode3.text != null) { - if (vnode3.text !== "") element.textContent = vnode3.text - else vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] - } - if (vnode3.children != null) { - var children3 = vnode3.children - createNodes(element, children3, 0, children3.length, hooks, null, ns) - if (vnode3.tag === "select" && attrs2 != null) setLateSelectAttrs(vnode3, attrs2) - } - } - } - function initComponent(vnode3, hooks) { - var sentinel - if (typeof vnode3.tag.view === "function") { - vnode3.state = Object.create(vnode3.tag) - sentinel = vnode3.state.view - if (sentinel.$$reentrantLock$$ != null) return - sentinel.$$reentrantLock$$ = true - } else { - vnode3.state = void 0 - sentinel = vnode3.tag - if (sentinel.$$reentrantLock$$ != null) return - sentinel.$$reentrantLock$$ = true - vnode3.state = (vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === "function") ? new vnode3.tag(vnode3) : vnode3.tag(vnode3) - } - initLifecycle(vnode3.state, vnode3, hooks) - if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) - vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) - if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") - sentinel.$$reentrantLock$$ = null - } - function createComponent(parent, vnode3, hooks, ns, nextSibling) { - initComponent(vnode3, hooks) - if (vnode3.instance != null) { - createNode(parent, vnode3.instance, hooks, ns, nextSibling) - vnode3.dom = vnode3.instance.dom - vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0 - } - else { - vnode3.domSize = 0 - } - } - //update - /** - * @param {Element|Fragment} parent - the parent element - * @param {Vnode[] | null} old - the list of vnodes of the last `render()` call for - * this part of the tree - * @param {Vnode[] | null} vnodes - as above, but for the current `render()` call. - * @param {Function[]} hooks - an accumulator of post-render hooks (oncreate/onupdate) - * @param {Element | null} nextSibling - the next0 DOM node if we're dealing with a - * fragment that is not the last item in its - * parent - * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any - * @returns void - */ - // This function diffs and patches lists of vnodes, both keyed and unkeyed. - // - // We will: - // - // 1. describe its general structure - // 2. focus on the diff algorithm optimizations - // 3. discuss DOM node operations. - // ## Overview: - // - // The updateNodes() function: - // - deals with trivial cases - // - determines whether the lists are keyed or unkeyed based on the first non-null node - // of each list. - // - diffs them and patches the DOM if needed (that's the brunt of the code) - // - manages the leftovers: after diffing, are there: - // - old nodes left to remove? - // - new nodes to insert? - // deal with them! - // - // The lists are only iterated over once, with an exception for the nodes in `old` that - // are visited in the fourth part of the diff and in the `removeNodes` loop. - // ## Diffing - // - // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837 - // may be good for context on longest increasing subsequence-based logic for moving nodes. - // - // In order to diff keyed lists, one has to - // - // 1) match0 nodes in both lists, per key, and update them accordingly - // 2) create the nodes present in the new list, but absent in the old one - // 3) remove the nodes present in the old list, but absent in the new one - // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. - // - // To achieve 1) one can create a dictionary of keys => index0 (for the old list), then1 iterate - // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using - // the map. - // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new - // and must be created. - // For the removals, we actually remove the nodes that have been updated from the old list. - // The nodes that remain in that list after 1) and 2) have been performed can be safely removed. - // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS) - // algorithm. - // - // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going - // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices - // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would - // match0 the above lists, for example). - // - // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We - // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. - // - // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually - // the longest increasing subsequence *of old nodes still present in the new list*). - // - // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation - // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, - // the `LIS` and a temporary one to create the LIS). - // - // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of - // the LIS and can be updated without moving them. - // - // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with - // the exception of the last node if the list is fully reversed). - // - // ## Finding the next0 sibling. - // - // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. - // When the list is being traversed top-down, at any index0, the DOM nodes up to the previous - // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old - // list. The next0 sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. - // - // In the other scenarios (swaps, upwards traversal, map-based diff), - // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the - // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node - // as the next0 sibling (cached in the `nextSibling` variable). - // ## DOM node moves - // - // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However, - // this is not the case if the node moved (second and fourth part of the diff algo). We move - // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling` - // variable rather than fetching it using `getNextSibling()`. - // - // The fourth part of the diff currently inserts nodes unconditionally, leading to issues - // like #1791 and #1999. We need to be smarter about those situations where adjascent old - // nodes remain together in the new list in a way that isn't covered by parts one and - // three of the diff algo. - function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { - if (old === vnodes || old == null && vnodes == null) return - else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) - else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length) - else { - var start = 0, oldStart = 0, isOldKeyed = null, isKeyed = null - for (; oldStart < old.length; oldStart++) { - if (old[oldStart] != null) { - isOldKeyed = old[oldStart].key != null - break - } - } - for (; start < vnodes.length; start++) { - if (vnodes[start] != null) { - isKeyed = vnodes[start].key != null - break - } - } - if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls - if (isOldKeyed !== isKeyed) { - removeNodes(old, oldStart, old.length) - createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - } else if (!isKeyed) { - // Don't index0 past the end of either list (causes deopts). - var commonLength = old.length < vnodes.length ? old.length : vnodes.length - // Rewind if necessary to the first non-null index0 on either side. - // We could alternatively either explicitly create or remove nodes when `start !== oldStart` - // but that would be optimizing for sparse lists which are more rare than dense ones. - start = start < oldStart ? start : oldStart - for (; start < commonLength; start++) { - o = old[start] - v = vnodes[start] - if (o === v || o == null && v == null) continue - else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling)) - else if (v == null) removeNode(o) - else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns) - } - if (old.length > commonLength) removeNodes(old, start, old.length) - if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - } else { - // keyed diff - var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling - // bottom-up - while (oldEnd >= oldStart && end >= start) { - oe = old[oldEnd] - ve = vnodes[end] - if (oe == null) oldEnd-- - else if (ve == null) end-- - else if (oe.key === ve.key) { - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldEnd--, end-- - } else { - break - } - } - // top-down - while (oldEnd >= oldStart && end >= start) { - o = old[oldStart] - v = vnodes[start] - if (o == null) oldStart++ - else if (v == null) start++ - else if (o.key === v.key) { - oldStart++, start++ - if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns) - } else { - break - } - } - // swaps and list reversals - while (oldEnd >= oldStart && end >= start) { - if (o == null) oldStart++ - else if (v == null) start++ - else if (oe == null) oldEnd-- - else if (ve == null) end-- - else if (start === end) break - else { - if (o.key !== ve.key || oe.key !== v.key) break - topSibling = getNextSibling(old, oldStart, nextSibling) - insertNode(parent, toFragment(oe), topSibling) - if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns) - if (++start <= --end) insertNode(parent, toFragment(o), nextSibling) - if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldStart++; oldEnd-- - } - oe = old[oldEnd] - ve = vnodes[end] - o = old[oldStart] - v = vnodes[start] - } - // bottom up once again - while (oldEnd >= oldStart && end >= start) { - if (oe == null) oldEnd-- - else if (ve == null) end-- - else if (oe.key === ve.key) { - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldEnd--, end-- - } else { - break - } - oe = old[oldEnd] - ve = vnodes[end] - } - if (start > end) removeNodes(old, oldStart, oldEnd + 1) - else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) - else { - // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul - var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices - for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1 - for (i = end; i >= start; i--) { - if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1) - ve = vnodes[i] - if (ve != null) { - var oldIndex = map[ve.key] - if (oldIndex != null) { - pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered - oldIndices[i-start] = oldIndex - oe = old[oldIndex] - old[oldIndex] = null - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - matched++ - } - } - } - nextSibling = originalNextSibling - if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1) - if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) - else { - if (pos === -1) { - // the indices of the indices of the items that are part of the - // longest increasing subsequence in the oldIndices list - lisIndices = makeLisIndices(oldIndices) - li = lisIndices.length - 1 - for (i = end; i >= start; i--) { - v = vnodes[i] - if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) - else { - if (lisIndices[li] === i - start) li-- - else insertNode(parent, toFragment(v), nextSibling) - } - if (v.dom != null) nextSibling = vnodes[i].dom - } - } else { - for (i = end; i >= start; i--) { - v = vnodes[i] - if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) - if (v.dom != null) nextSibling = vnodes[i].dom - } - } - } - } - } - } - } - function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { - var oldTag = old.tag, tag = vnode3.tag - if (oldTag === tag) { - vnode3.state = old.state - vnode3.events = old.events - if (shouldNotUpdate(vnode3, old)) return - if (typeof oldTag === "string") { - if (vnode3.attrs != null) { - updateLifecycle(vnode3.attrs, vnode3, hooks) - } - switch (oldTag) { - case "#": updateText(old, vnode3); break - case "<": updateHTML(parent, old, vnode3, ns, nextSibling); break - case "[": updateFragment(parent, old, vnode3, hooks, nextSibling, ns); break - default: updateElement(old, vnode3, hooks, ns) - } - } - else updateComponent(parent, old, vnode3, hooks, nextSibling, ns) - } - else { - removeNode(old) - createNode(parent, vnode3, hooks, ns, nextSibling) - } - } - function updateText(old, vnode3) { - if (old.children.toString() !== vnode3.children.toString()) { - old.dom.nodeValue = vnode3.children - } - vnode3.dom = old.dom - } - function updateHTML(parent, old, vnode3, ns, nextSibling) { - if (old.children !== vnode3.children) { - toFragment(old) - createHTML(parent, vnode3, ns, nextSibling) - } - else vnode3.dom = old.dom, vnode3.domSize = old.domSize - } - function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { - updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns) - var domSize = 0, children3 = vnode3.children - vnode3.dom = null - if (children3 != null) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i] - if (child != null && child.dom != null) { - if (vnode3.dom == null) vnode3.dom = child.dom - domSize += child.domSize || 1 - } - } - if (domSize !== 1) vnode3.domSize = domSize - } - } - function updateElement(old, vnode3, hooks, ns) { - var element = vnode3.dom = old.dom - ns = getNameSpace(vnode3) || ns - if (vnode3.tag === "textarea") { - if (vnode3.attrs == null) vnode3.attrs = {} - if (vnode3.text != null) { - vnode3.attrs.value = vnode3.text //FIXME handle0 multiple children3 - vnode3.text = undefined - } - } - updateAttrs(vnode3, old.attrs, vnode3.attrs, ns) - if (vnode3.attrs != null && vnode3.attrs.contenteditable != null) { - setContentEditable(vnode3) - } - else if (old.text != null && vnode3.text != null && vnode3.text !== "") { - if (old.text.toString() !== vnode3.text.toString()) old.dom.firstChild.nodeValue = vnode3.text - } - else { - if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] - if (vnode3.text != null) vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] - updateNodes(element, old.children, vnode3.children, hooks, null, ns) - } - } - function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { - vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) - if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") - updateLifecycle(vnode3.state, vnode3, hooks) - if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks) - if (vnode3.instance != null) { - if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling) - else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns) - vnode3.dom = vnode3.instance.dom - vnode3.domSize = vnode3.instance.domSize - } - else if (old.instance != null) { - removeNode(old.instance) - vnode3.dom = undefined - vnode3.domSize = 0 - } - else { - vnode3.dom = old.dom - vnode3.domSize = old.domSize - } - } - function getKeyMap(vnodes, start, end) { - var map = Object.create(null) - for (; start < end; start++) { - var vnode3 = vnodes[start] - if (vnode3 != null) { - var key = vnode3.key - if (key != null) map[key] = start - } - } - return map - } - // Lifted from ivi https://github.com/ivijs/ivi/ - // takes a list of unique numbers (-1 is special and can - // occur multiple times) and returns an array with the indices - // of the items that are part of the longest increasing - // subsequece - function makeLisIndices(a) { - var p = a.slice() - var result = [] - result.push(0) - var u - var v - for (var i = 0, il = a.length; i < il; ++i) { - if (a[i] === -1) { - continue - } - var j = result[result.length - 1] - if (a[j] < a[i]) { - p[i] = j - result.push(i) - continue - } - u = 0 - v = result.length - 1 - while (u < v) { - var c = ((u + v) / 2) | 0 // eslint-disable-line no-bitwise - if (a[result[c]] < a[i]) { - u = c + 1 - } - else { - v = c - } - } - if (a[i] < a[result[u]]) { - if (u > 0) { - p[i] = result[u - 1] - } - result[u] = i - } - } - u = result.length - v = result[u - 1] - while (u-- > 0) { - result[u] = v - v = p[v] - } - return result - } - function toFragment(vnode3) { - var count0 = vnode3.domSize - if (count0 != null || vnode3.dom == null) { - var fragment = $doc.createDocumentFragment() - if (count0 > 0) { - var dom = vnode3.dom - while (--count0) fragment.appendChild(dom.nextSibling) - fragment.insertBefore(dom, fragment.firstChild) - } - return fragment - } - else return vnode3.dom - } - function getNextSibling(vnodes, i, nextSibling) { - for (; i < vnodes.length; i++) { - if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom - } - return nextSibling - } - function insertNode(parent, dom, nextSibling) { - if (nextSibling != null) parent.insertBefore(dom, nextSibling) - else parent.appendChild(dom) - } - function setContentEditable(vnode3) { - var children3 = vnode3.children - if (children3 != null && children3.length === 1 && children3[0].tag === "<") { - var content = children3[0].children - if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content - } - else if (vnode3.text != null || children3 != null && children3.length !== 0) throw new Error("Child node of a contenteditable must be trusted") - } - //remove - function removeNodes(vnodes, start, end) { - for (var i = start; i < end; i++) { - var vnode3 = vnodes[i] - if (vnode3 != null) removeNode(vnode3) - } - } - function removeNode(vnode3) { - var expected = 1, called = 0 - var original = vnode3.state - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") { - var result = callHook.call(vnode3.state.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { - expected++ - result.then(continuation, continuation) - } - } - if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") { - var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { - expected++ - result.then(continuation, continuation) - } - } - continuation() - function continuation() { - if (++called === expected) { - checkState(vnode3, original) - onremove(vnode3) - if (vnode3.dom) { - var parent = vnode3.dom.parentNode - var count0 = vnode3.domSize || 1 - while (--count0) parent.removeChild(vnode3.dom.nextSibling) - parent.removeChild(vnode3.dom) - } - } - } - } - function onremove(vnode3) { - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onremove === "function") callHook.call(vnode3.state.onremove, vnode3) - if (vnode3.attrs && typeof vnode3.attrs.onremove === "function") callHook.call(vnode3.attrs.onremove, vnode3) - if (typeof vnode3.tag !== "string") { - if (vnode3.instance != null) onremove(vnode3.instance) - } else { - var children3 = vnode3.children - if (Array.isArray(children3)) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i] - if (child != null) onremove(child) - } - } - } - } - //attrs2 - function setAttrs(vnode3, attrs2, ns) { - for (var key in attrs2) { - setAttr(vnode3, key, null, attrs2[key], ns) - } - } - function setAttr(vnode3, key, old, value, ns) { - if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode3, key)) && typeof value !== "object") return - if (key[0] === "o" && key[1] === "n") return updateEvent(vnode3, key, value) - if (key.slice(0, 6) === "xlink:") vnode3.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value) - else if (key === "style") updateStyle(vnode3.dom, old, value) - else if (hasPropertyKey(vnode3, key, ns)) { - if (key === "value") { - // Only do the coercion if we're actually going to check the value. - /* eslint-disable no-implicit-coercion */ - //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if ((vnode3.tag === "input" || vnode3.tag === "textarea") && vnode3.dom.value === "" + value && vnode3.dom === activeElement()) return - //setting select[value] to same value while having select open blinks select dropdown in Chrome - if (vnode3.tag === "select" && old !== null && vnode3.dom.value === "" + value) return - //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode3.tag === "option" && old !== null && vnode3.dom.value === "" + value) return - /* eslint-enable no-implicit-coercion */ - } - // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error1 will occur. - if (vnode3.tag === "input" && key === "type") vnode3.dom.setAttribute(key, value) - else vnode3.dom[key] = value - } else { - if (typeof value === "boolean") { - if (value) vnode3.dom.setAttribute(key, "") - else vnode3.dom.removeAttribute(key) - } - else vnode3.dom.setAttribute(key === "className" ? "class" : key, value) - } - } - function removeAttr(vnode3, key, old, ns) { - if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return - if (key[0] === "o" && key[1] === "n" && !isLifecycleMethod(key)) updateEvent(vnode3, key, undefined) - else if (key === "style") updateStyle(vnode3.dom, old, null) - else if ( - hasPropertyKey(vnode3, key, ns) - && key !== "className" - && !(key === "value" && ( - vnode3.tag === "option" - || vnode3.tag === "select" && vnode3.dom.selectedIndex === -1 && vnode3.dom === activeElement() - )) - && !(vnode3.tag === "input" && key === "type") - ) { - vnode3.dom[key] = null - } else { - var nsLastIndex = key.indexOf(":") - if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1) - if (old !== false) vnode3.dom.removeAttribute(key === "className" ? "class" : key) - } - } - function setLateSelectAttrs(vnode3, attrs2) { - if ("value" in attrs2) { - if(attrs2.value === null) { - if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null - } else { - var normalized = "" + attrs2.value // eslint-disable-line no-implicit-coercion - if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { - vnode3.dom.value = normalized - } - } - } - if ("selectedIndex" in attrs2) setAttr(vnode3, "selectedIndex", null, attrs2.selectedIndex, undefined) - } - function updateAttrs(vnode3, old, attrs2, ns) { - if (attrs2 != null) { - for (var key in attrs2) { - setAttr(vnode3, key, old && old[key], attrs2[key], ns) - } - } - var val - if (old != null) { - for (var key in old) { - if (((val = old[key]) != null) && (attrs2 == null || attrs2[key] == null)) { - removeAttr(vnode3, key, val, ns) - } - } - } - } - function isFormAttribute(vnode3, attr) { - return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode3.dom === activeElement() || vnode3.tag === "option" && vnode3.dom.parentNode === $doc.activeElement - } - function isLifecycleMethod(attr) { - return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" - } - function hasPropertyKey(vnode3, key, ns) { - // Filter out namespaced keys - return ns === undefined && ( - // If it's a custom element, just keep it. - vnode3.tag.indexOf("-") > -1 || vnode3.attrs != null && vnode3.attrs.is || - // If it's a normal element, let's try to avoid a few browser bugs. - key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type" - // Defer the property check until *after* we check everything. - ) && key in vnode3.dom - } - //style - var uppercaseRegex = /[A-Z]/g - function toLowerCase(capital) { return "-" + capital.toLowerCase() } - function normalizeKey(key) { - return key[0] === "-" && key[1] === "-" ? key : - key === "cssFloat" ? "float" : - key.replace(uppercaseRegex, toLowerCase) - } - function updateStyle(element, old, style) { - if (old === style) { - // Styles are equivalent, do nothing. - } else if (style == null) { - // New style is missing, just clear it. - element.style.cssText = "" - } else if (typeof style !== "object") { - // New style is a string, let engine deal with patching. - element.style.cssText = style - } else if (old == null || typeof old !== "object") { - // `old` is missing or a string, `style` is an object. - element.style.cssText = "" - // Add new style properties - for (var key in style) { - var value = style[key] - if (value != null) element.style.setProperty(normalizeKey(key), String(value)) - } - } else { - // Both old & new are (different) objects. - // Update style properties that have changed - for (var key in style) { - var value = style[key] - if (value != null && (value = String(value)) !== String(old[key])) { - element.style.setProperty(normalizeKey(key), value) - } - } - // Remove style properties that no longer exist - for (var key in old) { - if (old[key] != null && style[key] == null) { - element.style.removeProperty(normalizeKey(key)) - } - } - } - } - // Here's an explanation of how this works: - // 1. The event names are always (by design) prefixed by `on`. - // 2. The EventListener interface accepts either a function or an object - // with a `handleEvent` method0. - // 3. The object does not inherit from `Object.prototype`, to avoid - // any potential interference with that (e.g. setters). - // 4. The event name is remapped to the handler0 before calling it. - // 5. In function-based event handlers, `ev.target === this`. We replicate - // that below. - // 6. In function-based event handlers, `return false` prevents the default - // action and stops event propagation. We replicate that below. - function EventDict() {} - EventDict.prototype = Object.create(null) - EventDict.prototype.handleEvent = function (ev) { - var handler0 = this["on" + ev.type] - var result - if (typeof handler0 === "function") result = handler0.call(ev.currentTarget, ev) - else if (typeof handler0.handleEvent === "function") handler0.handleEvent(ev) - if (ev.redraw === false) ev.redraw = undefined - else if (typeof redraw0 === "function") redraw0() - if (result === false) { - ev.preventDefault() - ev.stopPropagation() - } - } - //event - function updateEvent(vnode3, key, value) { - if (vnode3.events != null) { - if (vnode3.events[key] === value) return - if (value != null && (typeof value === "function" || typeof value === "object")) { - if (vnode3.events[key] == null) vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = value - } else { - if (vnode3.events[key] != null) vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = undefined - } - } else if (value != null && (typeof value === "function" || typeof value === "object")) { - vnode3.events = new EventDict() - vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = value - } - } - //lifecycle - function initLifecycle(source, vnode3, hooks) { - if (typeof source.oninit === "function") callHook.call(source.oninit, vnode3) - if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode3)) - } - function updateLifecycle(source, vnode3, hooks) { - if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode3)) - } - function shouldNotUpdate(vnode3, old) { - do { - if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === "function") { - var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old) - if (force !== undefined && !force) break - } - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeupdate === "function") { - var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old) - if (force !== undefined && !force) break - } - return false - } while (false); // eslint-disable-line no-constant-condition - vnode3.dom = old.dom - vnode3.domSize = old.domSize - vnode3.instance = old.instance - return true - } - function render(dom, vnodes) { - if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") - var hooks = [] - var active = activeElement() - var namespace = dom.namespaceURI - // First time rendering0 into a node clears it out - if (dom.vnodes == null) dom.textContent = "" - vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) - updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) - dom.vnodes = vnodes - // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement - if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() - for (var i = 0; i < hooks.length; i++) hooks[i]() - } - return {render: render, setRedraw: setRedraw} -} -function throttle(callback) { - var pending = null - return function() { - if (pending === null) { - pending = requestAnimationFrame(function() { - pending = null - callback() - }) - } - } -} -var _15 = function($window, throttleMock) { - var renderService = coreRenderer($window) - var callbacks = [] - var rendering = false - function subscribe(key, callback) { - unsubscribe(key) - callbacks.push(key, callback) - } - function unsubscribe(key) { - var index = callbacks.indexOf(key) - if (index > -1) callbacks.splice(index, 2) - } - function sync() { - if (rendering) throw new Error("Nested m.redraw.sync() call") - rendering = true - for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {if (typeof console !== "undefined") console.error(e)} - rendering = false - } - var redraw = (throttleMock || throttle)(sync) - redraw.sync = sync - renderService.setRedraw(redraw) - return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} -} -var redrawService = _15(window) -requestService.setCompletionCallback(redrawService.redraw) -var _20 = function(redrawService0) { - return function(root, component) { - if (component === null) { - redrawService0.render(root, []) - redrawService0.unsubscribe(root) - return - } - - if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode") - - var run0 = function() { - redrawService0.render(root, Vnode(component)) - } - redrawService0.subscribe(root, run0) - run0() - } -} -m.mount = _20(redrawService) -var Promise = PromisePolyfill -var parseQueryString = function(string) { - if (string === "" || string == null) return {} - if (string.charAt(0) === "?") string = string.slice(1) - var entries = string.split("&"), data2 = {}, counters = {} - for (var i = 0; i < entries.length; i++) { - var entry = entries[i].split("=") - var key2 = decodeURIComponent(entry[0]) - var value0 = entry.length === 2 ? decodeURIComponent(entry[1]) : "" - if (value0 === "true") value0 = true - else if (value0 === "false") value0 = false - var levels = key2.split(/\]\[?|\[/) - var cursor = data2 - if (key2.indexOf("[") > -1) levels.pop() - for (var j0 = 0; j0 < levels.length; j0++) { - var level = levels[j0], nextLevel = levels[j0 + 1] - var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) - var isValue = j0 === levels.length - 1 - if (level === "") { - var key2 = levels.slice(0, j0).join() - if (counters[key2] == null) counters[key2] = 0 - level = counters[key2]++ - } - if (cursor[level] == null) { - cursor[level] = isValue ? value0 : isNumber ? [] : {} - } - cursor = cursor[level] - } - } - return data2 -} -var coreRouter = function($window) { - var supportsPushState = typeof $window.history.pushState === "function" - var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout - function normalize(fragment0) { - var data1 = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) - if (fragment0 === "pathname" && data1[0] !== "/") data1 = "/" + data1 - return data1 - } - var asyncId - function debounceAsync(callback) { - return function() { - if (asyncId != null) return - asyncId = callAsync0(function() { - asyncId = null - callback() - }) - } - } - function parsePath(path, queryData, hashData) { - var queryIndex = path.indexOf("?") - var hashIndex = path.indexOf("#") - var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length - if (queryIndex > -1) { - var queryEnd = hashIndex > -1 ? hashIndex : path.length - var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) - for (var key1 in queryParams) queryData[key1] = queryParams[key1] - } - if (hashIndex > -1) { - var hashParams = parseQueryString(path.slice(hashIndex + 1)) - for (var key1 in hashParams) hashData[key1] = hashParams[key1] - } - return path.slice(0, pathEnd) - } - var router = {prefix: "#!"} - router.getPath = function() { - var type2 = router.prefix.charAt(0) - switch (type2) { - case "#": return normalize("hash").slice(router.prefix.length) - case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") - default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") - } - } - router.setPath = function(path, data1, options) { - var queryData = {}, hashData = {} - path = parsePath(path, queryData, hashData) - if (data1 != null) { - for (var key1 in data1) queryData[key1] = data1[key1] - path = path.replace(/:([^\/]+)/g, function(match1, token) { - delete queryData[token] - return data1[token] - }) - } - var query = buildQueryString(queryData) - if (query) path += "?" + query - var hash = buildQueryString(hashData) - if (hash) path += "#" + hash - if (supportsPushState) { - var state = options ? options.state : null - var title = options ? options.title : null - $window.onpopstate() - if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) - else $window.history.pushState(state, title, router.prefix + path) - } - else $window.location.href = router.prefix + path - } - router.defineRoutes = function(routes, resolve, reject) { - function resolveRoute() { - var path = router.getPath() - var params = {} - var pathname = parsePath(path, params, params) - var state = $window.history.state - if (state != null) { - for (var k in state) params[k] = state[k] - } - for (var route0 in routes) { - var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") - if (matcher.test(pathname)) { - pathname.replace(matcher, function() { - var keys = route0.match(/:[^\/]+/g) || [] - var values = [].slice.call(arguments, 1, -2) - for (var i = 0; i < keys.length; i++) { - params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) - } - resolve(routes[route0], params, path, route0) - }) - return - } - } - reject(path, params) - } - if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) - else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute - resolveRoute() - } - return router -} -var _24 = function($window, redrawService0) { - var routeService = coreRouter($window) - var identity = function(v0) {return v0} - var render1, component, attrs3, currentPath, lastUpdate - var route = function(root, defaultRoute, routes) { - if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") - function run1() { - if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) - } - var redraw3 = function() { - run1() - redraw3 = redrawService0.redraw - } - redrawService0.subscribe(root, run1) - var bail = function(path) { - if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) - else throw new Error("Could not resolve default route " + defaultRoute) - } - routeService.defineRoutes(routes, function(payload, params, path) { - var update = lastUpdate = function(routeResolver, comp) { - if (update !== lastUpdate) return - component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" - attrs3 = params, currentPath = path, lastUpdate = null - render1 = (routeResolver.render || identity).bind(routeResolver) - redraw3() - } - if (payload.view || typeof payload === "function") update({}, payload) - else { - if (payload.onmatch) { - Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { - update(payload, resolved) - }, bail) - } - else update(payload, "div") - } - }, bail) - } - route.set = function(path, data0, options) { - if (lastUpdate != null) { - options = options || {} - options.replace = true - } - lastUpdate = null - routeService.setPath(path, data0, options) - } - route.get = function() {return currentPath} - route.prefix = function(prefix) {routeService.prefix = prefix} - var link = function(options, vnode5) { - vnode5.dom.setAttribute("href", routeService.prefix + vnode5.attrs.href) - vnode5.dom.onclick = function(e) { - if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return - e.preventDefault() - e.redraw = false - var href = this.getAttribute("href") - if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length) - route.set(href, undefined, options) - } - } - route.link = function(args0) { - if (args0.tag == null) return link.bind(link, args0) - return link({}, args0) - } - route.param = function(key0) { - if(typeof attrs3 !== "undefined" && typeof key0 !== "undefined") return attrs3[key0] - return attrs3 - } - return route -} -m.route = _24(window, redrawService) -var _31 = coreRenderer(window) -m.render = _31.render -m.redraw = redrawService.redraw -m.request = requestService.request -m.jsonp = requestService.jsonp -m.parseQueryString = parseQueryString -m.buildQueryString = buildQueryString -m.version = "2.0.0-rc.4" -m.vnode = Vnode -m.PromisePolyfill = PromisePolyfill - -export default m -var _m = m.m,_trust = m.trust,_fragment = m.fragment,_mount = m.mount,_route = m.route,_render = m.render,_redraw = m.redraw,_request = m.request,_jsonp = m.jsonp,_parseQueryString = m.parseQueryString,_buildQueryString = m.buildQueryString,_version = m.version,_vnode = m.vnode,_PromisePolyfill = m.PromisePolyfill -export {_m as m,_trust as trust,_fragment as fragment,_mount as mount,_route as route,_render as render,_redraw as redraw,_request as request,_jsonp as jsonp,_parseQueryString as parseQueryString,_buildQueryString as buildQueryString,_version as version,_vnode as vnode,_PromisePolyfill as PromisePolyfill} \ No newline at end of file diff --git a/ospec/change-log.md b/ospec/change-log.md index df83a96b..3e6c7f6d 100644 --- a/ospec/change-log.md +++ b/ospec/change-log.md @@ -1,32 +1,42 @@ -# Change Log for ospec +# Change log for ospec +- [Upcoming](#upcoming) +- [3.1.0](#310) +- [3.0.1](#301) +- [3.0.0](#300) +- [2.1.0](#210) +- [2.0.0](#200) +- [1.4.1](#141) +- [1.4.0](#140) +- [1.3 and earlier](#13-and-earlier) -## Upcoming... -_2018-xx-yy_ +### Upcoming... + +### 3.1.0 - ospec: Test results now include `.message` and `.context` regardless of whether the test passed or failed. (#2227 @robertakarobin) -- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call. +- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call. (#2221 @isiahmeadows) - Added `.throws` and `.notThrows` assertions to ospec. (#2255 @robertakarobin) +- Update `glob` dependency. -## 3.0.1 -_2018-06-30_ +### 3.0.1 -### Bug fix +#### Bug fix - Move `glob` from `devDependencies` to `dependencies`, fix the test runner ([#2186](https://github.com/MithrilJS/mithril.js/pull/2186) [@porsager](https://github.com/porsager) -## 3.0.0 -_2018-06-20_ -### Breaking +### 3.0.0 + +#### Breaking - Better input checking to prevent misuses of the library. Misues of the library will now throw errors, rather than report failures. This may uncover bugs in your test suites. Since it is potentially a disruptive update this change triggers a semver major bump. ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - Change the reserved character for hooks and test suite meta-information from `"__"` to `"\x01"`. Tests whose name start with `"\0x01"` will be rejected ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) -### Features +#### Features - Give async timeout a stack trace that points to the problematic test ([#2154](https://github.com/MithrilJS/mithril.js/pull/2154) [@gilbert](github.com/gilbert), [#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - deprecate the `timeout` parameter in async tests in favour of `o.timeout()` for setting the timeout delay. The `timeout` parameter still works for v3, and will be removed in v4 ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - add `o.defaultTimeout()` for setting the the timeout delay for the current spec and its children ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - adds the possibility to select more than one test with o.only ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) -### Bug fixes +#### Bug fixes - Detect duplicate calls to `done()` properly [#2162](https://github.com/MithrilJS/mithril.js/issues/2162) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - Don't try to report internal errors as assertion failures, throw them instead ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) - Don't ignore, silently, tests whose name start with the test suite meta-information sequence (was `"__"` up to this version) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) @@ -34,18 +44,18 @@ _2018-06-20_ - Catch exceptions thrown in synchronous tests and report them as assertion failures ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) - Fix a stack overflow when using `o.only()` with a large test suite ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) -## 2.1.0 -_2018-05-25_ -### Features +### 2.1.0 + +#### Features - Pinpoint the `o.only()` call site ([#2157](https://github.com/MithrilJS/mithril.js/pull/2157)) - Improved wording, spacing and color-coding of report messages and errors ([#2147](https://github.com/MithrilJS/mithril.js/pull/2147), [@maranomynet](https://github.com/maranomynet)) -### Bug fixes +#### Bug fixes - Convert the exectuable back to plain ES5 [#2160](https://github.com/MithrilJS/mithril.js/issues/2160) ([#2161](https://github.com/MithrilJS/mithril.js/pull/2161)) -## 2.0.0 -_2018-05-09_ +### 2.0.0 + - Added `--require` feature to the ospec executable ([#2144](https://github.com/MithrilJS/mithril.js/pull/2144), [@gilbert](https://github.com/gilbert)) - In Node.js, ospec only uses colors when the output is sent to a terminal ([#2143](https://github.com/MithrilJS/mithril.js/pull/2143)) - the CLI runner now accepts globs as arguments ([#2141](https://github.com/MithrilJS/mithril.js/pull/2141), [@maranomynet](https://github.com/maranomynet)) @@ -59,21 +69,22 @@ _2018-05-09_ -## 1.4.1 -_2018-05-03_ +### 1.4.1 + - Identical to v1.4.0, but with UNIX-style line endings so that BASH is happy. -## 1.4.0 -_2017-12-01_ +### 1.4.0 + - Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer)) - Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928)) - Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@RodericDay](https://github.com/RodericDay)) -## 1.3 and earlier +### 1.3 and earlier + - Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager)) - Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout)) - Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529)) diff --git a/ospec/esm.js b/ospec/esm.js deleted file mode 100644 index 4682c747..00000000 --- a/ospec/esm.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict" - -/* - -This script will create an esm compatible script -from the already compiled version of: - -- ospec.js > ospec.mjs - -*/ - -var fs = require("fs") - -var ospec = fs.readFileSync("ospec.js", "utf8") -fs.writeFileSync("ospec.mjs", - "export default " - + ospec.slice(ospec.indexOf("})") + 2) - + "()" -) diff --git a/ospec/ospec.mjs b/ospec/ospec.mjs deleted file mode 100644 index e6ea506d..00000000 --- a/ospec/ospec.mjs +++ /dev/null @@ -1,363 +0,0 @@ -export default (function init(name) { - var spec = {}, subjects = [], results, only = [], ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty - var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName - var globalTimeout = noTimeoutRightNow - var currentTestError = null - if (name != null) spec[name] = ctx = {} - - function o(subject, predicate) { - if (predicate === undefined) { - if (!isRunning()) throw new Error("Assertions should not occur outside test definitions") - return new Assert(subject) - } else { - if (isRunning()) throw new Error("Test definitions and hooks shouldn't be nested. To group tests use `o.spec()`") - subject = String(subject) - if (subject.charCodeAt(0) === 1) throw new Error("test names starting with '\\x01' are reserved for internal use") - ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error)) - } - } - o.before = hook("\x01before") - o.after = hook("\x01after") - o.beforeEach = hook("\x01beforeEach") - o.afterEach = hook("\x01afterEach") - o.specTimeout = function (t) { - if (isRunning()) throw new Error("o.specTimeout() can only be called before o.run()") - if (hasOwn.call(ctx, "\x01specTimeout")) throw new Error("A default timeout has already been defined in this context") - if (typeof t !== "number") throw new Error("o.specTimeout() expects a number as argument") - ctx["\x01specTimeout"] = t - } - o.new = init - o.spec = function(subject, predicate) { - var parent = ctx - ctx = ctx[unique(subject)] = {} - predicate() - ctx = parent - } - o.only = function(subject, predicate, silent) { - if (!silent) console.log( - highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n", - cStyle("red"), "" - ) - only.push(predicate) - o(subject, predicate) - } - o.spy = function(fn) { - var spy = function() { - spy.this = this - spy.args = [].slice.call(arguments) - spy.callCount++ - - if (fn) return fn.apply(this, arguments) - } - if (fn) - Object.defineProperties(spy, { - length: {value: fn.length}, - name: {value: fn.name} - }) - spy.args = [] - spy.callCount = 0 - return spy - } - o.cleanStackTrace = function(error) { - // For IE 10+ in quirks mode, and IE 9- in any mode, errors don't have a stack - if (error.stack == null) return "" - var i = 0, header = error.message ? error.name + ": " + error.message : error.name, stack - // some environments add the name and message to the stack trace - if (error.stack.indexOf(header) === 0) { - stack = error.stack.slice(header.length).split(/\r?\n/) - stack.shift() // drop the initial empty string - } else { - stack = error.stack.split(/\r?\n/) - } - if (ospecFileName == null) return stack.join("\n") - // skip ospec-related entries on the stack - while (stack[i] != null && stack[i].indexOf(ospecFileName) !== -1) i++ - // now we're in user code (or past the stack end) - return stack[i] - } - o.timeout = function(n) { - globalTimeout(n) - } - o.run = function(reporter) { - results = [] - start = new Date - test(spec, [], [], new Task(function() { - setTimeout(function () { - timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/) - if (typeof reporter === "function") reporter(results) - else { - var errCount = o.report(results) - if (hasProcess && errCount !== 0) process.exit(1) // eslint-disable-line no-process-exit - } - }) - }, null), 200 /*default timeout delay*/) - - function test(spec, pre, post, finalize, defaultDelay) { - if (hasOwn.call(spec, "\x01specTimeout")) defaultDelay = spec["\x01specTimeout"] - pre = [].concat(pre, spec["\x01beforeEach"] || []) - post = [].concat(spec["\x01afterEach"] || [], post) - series([].concat(spec["\x01before"] || [], Object.keys(spec).reduce(function(tasks, key) { - if (key.charCodeAt(0) !== 1 && (only.length === 0 || only.indexOf(spec[key].fn) !== -1 || !(spec[key] instanceof Task))) { - tasks.push(new Task(function(done) { - o.timeout(Infinity) - subjects.push(key) - var pop = new Task(function pop() {subjects.pop(), done()}, null) - if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop), defaultDelay) - else test(spec[key], pre, post, pop, defaultDelay) - }, null)) - } - return tasks - }, []), spec["\x01after"] || [], finalize), defaultDelay) - } - - function series(tasks, defaultDelay) { - var cursor = 0 - next() - - function next() { - if (cursor === tasks.length) return - - var task = tasks[cursor++] - var fn = task.fn - currentTestError = task.err - var timeout = 0, delay = defaultDelay, s = new Date - var current = cursor - var arg - - globalTimeout = setDelay - - var isDone = false - // public API, may only be called once from use code (or after returned Promise resolution) - function done(err) { - if (!isDone) isDone = true - else throw new Error("`" + arg + "()` should only be called once") - if (timeout === undefined) console.warn("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms\n" + o.cleanStackTrace(task.err)) - finalizeAsync(err) - } - // for internal use only - function finalizeAsync(err) { - if (err == null) { - if (task.err != null) succeed(new Assert) - } else { - if (err instanceof Error) fail(new Assert, err.message, err) - else fail(new Assert, String(err), null) - } - if (timeout !== undefined) timeout = clearTimeout(timeout) - if (current === cursor) next() - } - function startTimer() { - timeout = setTimeout(function() { - timeout = undefined - finalizeAsync("async test timed out after " + delay + "ms") - }, Math.min(delay, 2147483647)) - } - function setDelay (t) { - if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument") - delay = t - } - if (fn.length > 0) { - var body = fn.toString() - arg = (body.match(/^(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*=>/) || body.match(/\((?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*[,\)]/) || []).pop() - if (body.indexOf(arg) === body.lastIndexOf(arg)) { - var e = new Error - e.stack = "`" + arg + "()` should be called at least once\n" + o.cleanStackTrace(task.err) - throw e - } - try { - fn(done, setDelay) - } - catch (e) { - if (task.err != null) finalizeAsync(e) - // The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. - else throw e - } - if (timeout === 0) { - startTimer() - } - } else { - try{ - var p = fn() - if (p && p.then) { - startTimer() - p.then(function() { done() }, done) - } else { - nextTickish(next) - } - } catch (e) { - if (task.err != null) finalizeAsync(e) - // The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. - else throw e - } - } - globalTimeout = noTimeoutRightNow - } - } - } - function unique(subject) { - if (hasOwn.call(ctx, subject)) { - console.warn("A test or a spec named `" + subject + "` was already defined") - while (hasOwn.call(ctx, subject)) subject += "*" - } - return subject - } - function hook(name) { - return function(predicate) { - if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate) - ctx[name] = new Task(predicate, ensureStackTrace(new Error)) - } - } - - define("equals", "should equal", function(a, b) {return a === b}) - define("notEquals", "should not equal", function(a, b) {return a !== b}) - define("deepEquals", "should deep equal", deepEqual) - define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)}) - - function isArguments(a) { - if ("callee" in a) { - for (var i in a) if (i === "callee") return false - return true - } - } - function deepEqual(a, b) { - if (a === b) return true - if (a === null ^ b === null || a === undefined ^ b === undefined) return false // eslint-disable-line no-bitwise - if (typeof a === "object" && typeof b === "object") { - var aIsArgs = isArguments(a), bIsArgs = isArguments(b) - if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) { - for (var i in a) { - if ((!(i in b)) || !deepEqual(a[i], b[i])) return false - } - for (var i in b) { - if (!(i in a)) return false - } - return true - } - if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) { - var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b) - if (aKeys.length !== bKeys.length) return false - for (var i = 0; i < aKeys.length; i++) { - if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false - } - return true - } - if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime() - if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) { - for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false - } - return true - } - if (a.valueOf() === b.valueOf()) return true - } - return false - } - - function isRunning() {return results != null} - function Assert(value) { - this.value = value - this.i = results.length - results.push({pass: null, context: "", message: "Incomplete assertion in the test definition starting at...", error: currentTestError, testError: currentTestError}) - } - function Task(fn, err) { - this.fn = fn - this.err = err - } - function define(name, verb, compare) { - Assert.prototype[name] = function assert(value) { - if (compare(this.value, value)) succeed(this) - else fail(this, serialize(this.value) + "\n " + verb + "\n" + serialize(value)) - var self = this - return function(message) { - if (!self.pass) self.message = message + "\n\n" + self.message - } - } - } - function succeed(assertion) { - results[assertion.i].pass = true - } - function fail(assertion, message, error) { - results[assertion.i].pass = false - results[assertion.i].context = subjects.join(" > ") - results[assertion.i].message = message - results[assertion.i].error = error != null ? error : ensureStackTrace(new Error) - } - function serialize(value) { - if (hasProcess) return require("util").inspect(value) // eslint-disable-line global-require - if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value) - else if (typeof value === "function") return value.name || "" - try {return JSON.stringify(value)} catch (e) {return String(value)} - } - function noTimeoutRightNow() { - throw new Error("o.timeout must be called snchronously from within a test definition or a hook") - } - var colorCodes = { - red: "31m", - red2: "31;1m", - green: "32;1m" - } - function highlight(message, color) { - var code = colorCodes[color] || colorCodes.red; - return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + message + "\x1b[0m" : message) : "%c" + message + "%c " - } - function cStyle(color, bold) { - return hasProcess||!color ? "" : "color:"+color+(bold ? ";font-weight:bold" : "") - } - function ensureStackTrace(error) { - // mandatory to get a stack in IE 10 and 11 (and maybe other envs?) - if (error.stack === undefined) try { throw error } catch(e) {return e} - else return error - } - function getStackName(e, exp) { - return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null - } - - o.report = function (results) { - var errCount = 0 - for (var i = 0, r; r = results[i]; i++) { - if (r.pass == null) { - r.testError.stack = r.message + "\n" + o.cleanStackTrace(r.testError) - r.testError.message = r.message - throw r.testError - } - if (!r.pass) { - var stackTrace = o.cleanStackTrace(r.error) - var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1 - if (couldHaveABetterStackTrace) stackTrace = r.testError != null ? o.cleanStackTrace(r.testError) : r.error.stack || "" - console.error( - (hasProcess ? "\n" : "") + - highlight(r.context + ":", "red2") + "\n" + - highlight(r.message, "red") + - (stackTrace ? "\n" + stackTrace + "\n" : ""), - - cStyle("black", true), "", // reset to default - cStyle("red"), cStyle("black") - ) - errCount++ - } - } - var pl = results.length === 1 ? "" : "s" - var resultSummary = (errCount === 0) ? - highlight((pl ? "All " : "The ") + results.length + " assertion" + pl + " passed", "green"): - highlight(errCount + " out of " + results.length + " assertion" + pl + " failed", "red2") - var runningTime = " in " + Math.round(Date.now() - start) + "ms" - - console.log( - (hasProcess ? "โ€“โ€“โ€“โ€“โ€“โ€“\n" : "") + - (name ? name + ": " : "") + resultSummary + runningTime, - cStyle((errCount === 0 ? "green" : "red"), true), "" - ) - return errCount - } - - if (hasProcess) { - nextTickish = process.nextTick - } else { - nextTickish = function fakeFastNextTick(next) { - if (stack++ < 5000) next() - else setTimeout(next, stack = 0) - } - } - - return o -}) -() \ No newline at end of file diff --git a/ospec/package.json b/ospec/package.json index d6282329..e2a604d2 100644 --- a/ospec/package.json +++ b/ospec/package.json @@ -1,9 +1,8 @@ { "name": "ospec", - "version": "3.0.1", + "version": "3.1.0", "description": "Noiseless testing framework", "main": "ospec.js", - "module": "ospec.mjs", "directories": { "test": "tests" }, @@ -13,11 +12,8 @@ "bin": { "ospec": "./bin/ospec" }, - "scripts": { - "prepublishOnly": "node esm.js" - }, "repository": "MithrilJS/mithril.js", "dependencies": { - "glob": "^7.1.2" + "glob": "^7.1.3" } } diff --git a/package-lock.json b/package-lock.json index 0b5d9ce6..d064b4ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,39 +28,66 @@ "chalk": "^2.0.0", "esutils": "^2.0.2", "js-tokens": "^4.0.0" + } + }, + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "@iamstarkov/listr-update-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", + "integrity": "sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "chalk": "^1.0.0" } } } }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -68,9 +95,9 @@ "dev": true }, "acorn": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.7.tgz", + "integrity": "sha512-HNJNgE60C9eOTgn974Tlp3dpLZdUr+SoxxDwPaY9J/kDNOLQTkaDgwBUXAF4SSsrAwD9RpdxuHK/EbuF+W9Ahw==", "dev": true }, "acorn-jsx": { @@ -80,9 +107,9 @@ "dev": true }, "ajv": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", - "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.8.1.tgz", + "integrity": "sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -91,27 +118,17 @@ "uri-js": "^4.2.2" } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "dev": true, + "optional": true }, "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-regex": { @@ -126,6 +143,12 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -136,12 +159,6 @@ "normalize-path": "^2.1.1" } }, - "app-root-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", - "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", - "dev": true - }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -190,19 +207,31 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.2.tgz", - "integrity": "sha1-YSpKtF70KnDN6Aa62G7m2wR+g4U=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "^4.14.0" + "lodash": "^4.17.10" } }, "async-each": { @@ -362,43 +391,58 @@ "unset-value": "^1.0.0" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { - "callsites": "^0.2.0" + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } } }, "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "chardet": { @@ -458,20 +502,14 @@ } }, "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^1.0.1" + "restore-cursor": "^2.0.0" } }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", - "dev": true - }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -504,15 +542,6 @@ "object-visit": "^1.0.0" } }, - "collections": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/collections/-/collections-0.2.2.tgz", - "integrity": "sha1-HyMCay7zb5J+7MkB6ZxfDUj6M04=", - "dev": true, - "requires": { - "weak-map": "1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -529,9 +558,9 @@ "dev": true }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "dev": true }, "component-emitter": { @@ -559,44 +588,34 @@ "dev": true }, "cosmiconfig": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz", - "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "js-yaml": "^3.4.3", - "minimist": "^1.2.0", - "object-assign": "^4.0.1", - "os-homedir": "^1.0.1", - "parse-json": "^2.2.0", - "pinkie-promise": "^2.0.0", - "require-from-string": "^1.1.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "date-fns": { - "version": "1.28.5", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz", - "integrity": "sha1-JXz8RdMi30XvVlhmWWfuhBzXP68=", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, "debug": { @@ -608,13 +627,6 @@ "ms": "2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "optional": true - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -680,6 +692,20 @@ } } }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -695,10 +721,25 @@ "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", "dev": true }, + "email-addresses": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.0.3.tgz", + "integrity": "sha512-kUlSC06PVvvjlMRpNIl3kR1NRXLEe86VQ7N0bQeaCZb2g+InShCeHQp/JvyYNTugMnRN2NvJhHlc3q12MWbbpg==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -724,9 +765,9 @@ } }, "eslint": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.9.0.tgz", - "integrity": "sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.13.0.tgz", + "integrity": "sha512-nqD5WQMisciZC5EHZowejLKQjWGuFS5c70fxqSKlnDME+oz9zmE8KTlX+lHSg+/5wsC/kf9Q9eMkC8qS3oM2fg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -738,7 +779,7 @@ "eslint-scope": "^4.0.0", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", + "espree": "^5.0.0", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", @@ -746,9 +787,9 @@ "glob": "^7.1.2", "globals": "^11.7.0", "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "inquirer": "^6.1.0", - "is-resolvable": "^1.1.0", "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", @@ -758,10 +799,8 @@ "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", "progress": "^2.0.0", "regexpp": "^2.0.1", - "require-uncached": "^1.0.3", "semver": "^5.5.1", "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", @@ -775,43 +814,10 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -823,10 +829,20 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -847,15 +863,6 @@ "requires": { "ansi-regex": "^3.0.0" } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -890,9 +897,9 @@ "dev": true }, "espree": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", - "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", "dev": true, "requires": { "acorn": "^6.0.2", @@ -952,11 +959,20 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } }, "expand-brackets": { "version": "2.1.4", @@ -1134,6 +1150,33 @@ "object-assign": "^4.0.1" } }, + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", + "dev": true + }, + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", + "dev": true, + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=", + "dev": true, + "requires": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1157,6 +1200,12 @@ } } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -1167,19 +1216,14 @@ "graceful-fs": "^4.1.2", "rimraf": "~2.6.2", "write": "^0.2.1" - }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } } }, + "fn-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", + "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -1195,6 +1239,17 @@ "map-cache": "^0.2.2" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1221,7 +1276,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1242,12 +1298,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1262,17 +1320,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1389,7 +1450,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1401,6 +1463,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1415,6 +1478,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1422,12 +1486,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1446,6 +1512,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1526,7 +1593,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1538,6 +1606,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1623,7 +1692,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1659,6 +1729,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1678,6 +1749,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1721,12 +1793,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -1736,12 +1810,32 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "get-stream": { + "g-status": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/g-status/-/g-status-2.0.2.tgz", + "integrity": "sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "matcher": "^1.0.0", + "simple-git": "^1.85.0" + } + }, + "get-own-enumerable-property-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", "dev": true }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -1749,48 +1843,19 @@ "dev": true }, "gh-pages": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-0.12.0.tgz", - "integrity": "sha1-2VHj7Zi4VpnUsEGOsaFbGgSYjcE=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.0.1.tgz", + "integrity": "sha512-uFlk3bukljeiWKQ2XvPfjcSi/ou7IfoDf2p+Fj672saLAr8bnOdFVqI/JSgrSgInKpCg5BksxEwGUl++dbg8Dg==", "dev": true, "requires": { - "async": "2.1.2", - "commander": "2.9.0", + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^7.0.0", "globby": "^6.1.0", - "graceful-fs": "4.1.10", - "q": "1.4.1", - "q-io": "1.13.2", - "rimraf": "^2.5.4" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.10.tgz", - "integrity": "sha1-8tcgwiCS90Mih3XHXjYSYyUB8TE=", - "dev": true - } + "graceful-fs": "^4.1.11", + "rimraf": "^2.6.2" } }, "glob": { @@ -1829,49 +1894,55 @@ } }, "globals": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", - "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", "dev": true }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "handlebars": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { - "async": "^1.4.0", + "async": "^2.5.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -1922,6 +1993,16 @@ } } }, + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=", + "dev": true, + "requires": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1937,6 +2018,33 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1944,13 +2052,10 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true }, "inflight": { "version": "1.0.6", @@ -1969,67 +2074,32 @@ "dev": true }, "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" }, "dependencies": { - "ansi-escapes": { - "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2045,34 +2115,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2081,24 +2123,34 @@ "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" + "ansi-regex": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + } } } } @@ -2161,6 +2213,12 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -2173,15 +2231,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -2209,6 +2258,51 @@ "kind-of": "^3.0.2" } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -2224,10 +2318,10 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, "is-stream": { @@ -2356,6 +2450,12 @@ } } }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2368,6 +2468,15 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -2377,13 +2486,6 @@ "is-buffer": "^1.1.5" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2395,61 +2497,78 @@ } }, "lint-staged": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.4.tgz", - "integrity": "sha1-nKaWizDfv+gTZbenY81PSZKJZVM=", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.3.tgz", + "integrity": "sha512-6TGkikL1B+6mIOuSNq2TV6oP21IhPMnV8q0cf9oYZ296ArTVNcbFh1l1pfVOHHbBIYLlziWNsQ2q45/ffmJ4AA==", "dev": true, "requires": { - "app-root-path": "^2.0.0", - "cosmiconfig": "^1.1.0", - "execa": "^0.8.0", - "listr": "^0.12.0", - "lodash.chunk": "^4.2.0", - "minimatch": "^3.0.0", + "@iamstarkov/listr-update-renderer": "0.4.1", + "chalk": "^2.3.1", + "commander": "^2.14.1", + "cosmiconfig": "^5.0.2", + "debug": "^3.1.0", + "dedent": "^0.7.0", + "del": "^3.0.0", + "execa": "^1.0.0", + "find-parent-dir": "^0.3.0", + "g-status": "^2.0.2", + "is-glob": "^4.0.0", + "is-windows": "^1.0.2", + "listr": "^0.14.2", + "lodash": "^4.17.5", + "log-symbols": "^2.2.0", + "micromatch": "^3.1.8", "npm-which": "^3.0.1", "p-map": "^1.1.1", - "staged-git-files": "0.0.4" + "path-is-inside": "^1.0.2", + "pify": "^3.0.0", + "please-upgrade-node": "^3.0.2", + "staged-git-files": "1.1.2", + "string-argv": "^0.0.2", + "stringify-object": "^3.2.2", + "yup": "^0.26.10" }, "dependencies": { - "execa": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", - "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, "listr": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", - "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "figures": "^1.7.0", - "indent-string": "^2.1.0", + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", "is-promise": "^2.1.0", "is-stream": "^1.1.0", "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.2.0", - "listr-verbose-renderer": "^0.4.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "ora": "^0.2.3", - "p-map": "^1.1.1", - "rxjs": "^5.0.0-beta.11", - "stream-to-observable": "^0.1.0", - "strip-ansi": "^3.0.1" + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", + "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", + "dev": true + } } }, "listr-silent-renderer": { @@ -2459,9 +2578,9 @@ "dev": true }, "listr-update-renderer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", - "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -2470,28 +2589,55 @@ "figures": "^1.7.0", "indent-string": "^3.0.0", "log-symbols": "^1.0.2", - "log-update": "^1.0.2", + "log-update": "^2.3.0", "strip-ansi": "^3.0.1" }, "dependencies": { - "indent-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.1.0.tgz", - "integrity": "sha1-CP9DNGAziDmbMp5rlTjcejz13n0=", - "dev": true + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } } } }, "listr-verbose-renderer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz", - "integrity": "sha1-RNwBuww0oDxXIVTU0Izemx3FYg8=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-cursor": "^1.0.2", + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", "date-fns": "^1.27.2", - "figures": "^1.7.0" + "figures": "^2.0.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + } } }, "locater": { @@ -2506,12 +2652,6 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "lodash.chunk": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", - "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", - "dev": true - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -2519,38 +2659,23 @@ "dev": true }, "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^1.0.0" + "chalk": "^2.0.1" } }, "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", "dev": true, "requires": { - "ansi-escapes": "^1.0.0", - "cli-cursor": "^1.0.2" - } - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" } }, "map-cache": { @@ -2569,11 +2694,20 @@ } }, "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.0.tgz", + "integrity": "sha512-HduzIW2xApSXKXJSpCipSxKyvMbwRRa/TwMbepmlZziKdH8548WSoDP4SxzulEKjlo8BE39l+2fwJZuRKOln6g==", "dev": true }, + "matcher": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz", + "integrity": "sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.4" + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -2603,18 +2737,6 @@ } } }, - "mime": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", - "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", - "dev": true - }, - "mimeparse": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/mimeparse/-/mimeparse-0.1.4.tgz", - "integrity": "sha1-2vsCdSNw/SJgk64xUsJxrwGsJUo=", - "dev": true - }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -2742,10 +2864,22 @@ "remove-trailing-separator": "^1.0.1" } }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, "npm-path": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz", - "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", "dev": true, "requires": { "which": "^1.2.10" @@ -2833,10 +2967,13 @@ } }, "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } }, "optimist": { "version": "0.6.1", @@ -2870,24 +3007,6 @@ "wordwrap": "~1.0.0" } }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "cli-cursor": "^1.0.2", - "cli-spinners": "^0.1.2", - "object-assign": "^4.0.1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -2901,18 +3020,28 @@ "dev": true }, "p-map": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.1.1.tgz", - "integrity": "sha1-BfXkrpegaDcbwqXMhr+9vBnErno=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", "dev": true, "requires": { - "error-ex": "^1.2.0" + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "pascalcase": { @@ -2946,9 +3075,9 @@ "dev": true }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pinkie": { @@ -2978,11 +3107,14 @@ "integrity": "sha1-bw+xftqqSPIUQrOpdcBjEw8cPr0=", "dev": true }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true + "please-upgrade-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", + "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } }, "posix-character-classes": { "version": "0.1.1", @@ -2996,6 +3128,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -3003,49 +3141,41 @@ "dev": true }, "progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", - "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "property-expr": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz", + "integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - }, - "q-io": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/q-io/-/q-io-1.13.2.tgz", - "integrity": "sha1-7qEw1IHdteGqG8WmaFX3OR0G8AM=", + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "dev": true, "requires": { - "collections": "^0.2.0", - "mime": "^1.2.11", - "mimeparse": "^0.1.4", - "q": "^1.0.1", - "qs": "^1.2.1", - "url2": "^0.0.0" - }, - "dependencies": { - "qs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-1.2.2.tgz", - "integrity": "sha1-GbV/8k3CqZzh+L32r82ln472H4g=", - "dev": true - } + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" } }, "readable-stream": { @@ -3074,6 +3204,12 @@ "readable-stream": "^2.0.2" } }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -3108,35 +3244,10 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "resolve-url": { @@ -3146,13 +3257,13 @@ "dev": true }, "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -3161,23 +3272,29 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "run-async": { @@ -3190,12 +3307,12 @@ } }, "rxjs": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz", - "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", "dev": true, "requires": { - "symbol-observable": "^1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -3225,6 +3342,12 @@ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -3269,6 +3392,32 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-git": { + "version": "1.107.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.107.0.tgz", + "integrity": "sha512-t4OK1JRlp4ayKRfcW6owrWcRVLyHRUlhGd0uN6ZZTqfDq8a5XpcUdOKiGRNobHEuMtNqzp0vcJNvhYWwh5PsQA==", + "dev": true, + "requires": { + "debug": "^4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "slice-ansi": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", @@ -3383,6 +3532,15 @@ "kind-of": "^3.2.0" } }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, "source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", @@ -3407,9 +3565,9 @@ } }, "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -3446,9 +3604,9 @@ "dev": true }, "staged-git-files": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz", - "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz", + "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==", "dev": true }, "static-extend": { @@ -3472,10 +3630,16 @@ } } }, - "stream-to-observable": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", - "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", + "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", "dev": true }, "string-width": { @@ -3498,6 +3662,17 @@ "safe-buffer": "~5.1.0" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -3519,6 +3694,21 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -3526,20 +3716,26 @@ "dev": true }, "symbol-observable": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", - "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "synchronous-promise": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.6.tgz", + "integrity": "sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g==", "dev": true }, "table": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", - "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.2.tgz", + "integrity": "sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ==", "dev": true, "requires": { - "ajv": "^6.5.3", - "lodash": "^4.17.10", - "slice-ansi": "1.0.0", + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.0.0", "string-width": "^2.1.1" }, "dependencies": { @@ -3549,6 +3745,15 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -3556,11 +3761,13 @@ "dev": true }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, @@ -3586,14 +3793,14 @@ } }, "terser": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.10.11.tgz", - "integrity": "sha512-iruZ7j14oBbRYJC5cP0/vTU7YOWjN+J1ZskEGoF78tFzXdkK2hbCL/3TRZN8XB+MuvFhvOHMp7WkOCBO4VEL5g==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", + "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", "dev": true, "requires": { "commander": "~2.17.1", "source-map": "~0.6.1", - "source-map-support": "~0.5.6" + "source-map-support": "~0.5.9" }, "dependencies": { "commander": { @@ -3662,6 +3869,21 @@ "repeat-string": "^1.6.1" } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=", + "dev": true + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -3678,79 +3900,32 @@ } }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "commander": "~2.17.1", + "source-map": "~0.6.1" }, "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true, "optional": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -3786,6 +3961,12 @@ } } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -3847,12 +4028,6 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, - "url2": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/url2/-/url2-0.0.0.tgz", - "integrity": "sha1-Tqq9HVw6yQ1iq0SFyZhCKGWgSxo=", - "dev": true - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -3865,12 +4040,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "weak-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.0.tgz", - "integrity": "sha1-tm5Wqd8L0lp2u/G1FNsSkIBhSjc=", - "dev": true - }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", @@ -3886,6 +4055,49 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3901,11 +4113,19 @@ "mkdirp": "^0.5.1" } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "yup": { + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.26.10.tgz", + "integrity": "sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==", + "dev": true, + "requires": { + "@babel/runtime": "7.0.0", + "fn-name": "~2.0.1", + "lodash": "^4.17.10", + "property-expr": "^1.5.0", + "synchronous-promise": "^2.0.5", + "toposort": "^2.0.2" + } } } } diff --git a/package.json b/package.json index 45c0b9f7..174c3841 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,13 @@ "description": "A framework for building brilliant applications", "author": "Leo Horie", "license": "MIT", - "main": "mithril.js", - "module": "mithril.mjs", + "unpkg": "mithril.min.js", "repository": "MithrilJS/mithril.js", "scripts": { "dev": "node bundler/cli browser.js -output mithril.js -watch", - "build": "npm run build-browser & npm run build-min && npm run build-esm", + "build": "npm run build-browser & npm run build-min", "build-browser": "node bundler/cli browser.js -output mithril.js", "build-min": "node bundler/cli browser.js -output mithril.min.js -minify", - "build-esm": "node esm.js", "precommit": "lint-staged", "lintdocs": "node docs/lint", "gendocs": "node docs/generate", @@ -24,7 +22,7 @@ "cover": "istanbul cover --print both ospec/bin/ospec", "release": "npm version -m 'v%s'", "preversion": "npm run test", - "version": "npm run build && git add mithril.js mithril.min.js mithril.mjs mithril.min.mjs", + "version": "npm run build && git add mithril.js mithril.min.js", "postversion": "git push --follow-tags" }, "devDependencies": { @@ -32,15 +30,14 @@ "benchmark": "^2.1.4", "chokidar": "^2.0.4", "dedent": "^0.7.0", - "eslint": "^5.9.0", - "gh-pages": "^0.12.0", - "glob": "^7.1.2", + "eslint": "^5.13.0", + "gh-pages": "^2.0.1", "istanbul": "^0.4.5", - "lint-staged": "^4.0.4", + "lint-staged": "^8.1.3", "locater": "^1.3.0", - "marked": "^0.3.19", + "marked": "^0.6.0", "pinpoint": "^1.1.0", - "terser": "^3.10.11" + "terser": "^3.16.1" }, "bin": { "ospec": "./ospec/bin/ospec" diff --git a/pathname/assign.js b/pathname/assign.js new file mode 100644 index 00000000..607b630a --- /dev/null +++ b/pathname/assign.js @@ -0,0 +1,5 @@ +"use strict" + +module.exports = Object.assign || function(target, source) { + Object.keys(source).forEach(function(key) { target[key] = source[key] }) +} diff --git a/pathname/build.js b/pathname/build.js new file mode 100644 index 00000000..f5575fdc --- /dev/null +++ b/pathname/build.js @@ -0,0 +1,43 @@ +"use strict" + +var buildQueryString = require("../querystring/build") +var assign = require("./assign") + +// Returns `path` from `template` + `params` +module.exports = function(template, params) { + if ((/:([^\/\.-]+)(\.{3})?:/).test(template)) { + throw new SyntaxError("Template parameter names *must* be separated") + } + if (params == null) return template + var queryIndex = template.indexOf("?") + var hashIndex = template.indexOf("#") + var queryEnd = hashIndex < 0 ? template.length : hashIndex + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex + var path = template.slice(0, pathEnd) + var query = {} + + assign(query, params) + + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m, key, variadic) { + delete query[key] + // If no such parameter exists, don't interpolate it. + if (params[key] == null) return m + // Escape normal parameters, but not variadic ones. + return variadic ? params[key] : encodeURIComponent(String(params[key])) + }) + + // In case the template substitution adds new query/hash parameters. + var newQueryIndex = resolved.indexOf("?") + var newHashIndex = resolved.indexOf("#") + var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex + var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex + var result = resolved.slice(0, newPathEnd) + + if (queryIndex >= 0) result += "?" + template.slice(queryIndex, queryEnd) + if (newQueryIndex >= 0) result += (queryIndex < 0 ? "?" : "&") + resolved.slice(newQueryIndex, newQueryEnd) + var querystring = buildQueryString(query) + if (querystring) result += (queryIndex < 0 && newQueryIndex < 0 ? "?" : "&") + querystring + if (hashIndex >= 0) result += template.slice(hashIndex) + if (newHashIndex >= 0) result += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex) + return result +} diff --git a/pathname/compileTemplate.js b/pathname/compileTemplate.js new file mode 100644 index 00000000..390c1e56 --- /dev/null +++ b/pathname/compileTemplate.js @@ -0,0 +1,43 @@ +"use strict" + +var parsePathname = require("./parse") + +// Compiles a template into a function that takes a resolved path (without query +// strings) and returns an object containing the template parameters with their +// parsed values. This expects the input of the compiled template to be the +// output of `parsePathname`. Note that it does *not* remove query parameters +// specified in the template. +module.exports = function(template) { + var templateData = parsePathname(template) + var templateKeys = Object.keys(templateData.params) + var keys = [] + var regexp = new RegExp("^" + templateData.path.replace( + // I escape literal text so people can use things like `:file.:ext` or + // `:lang-:locale` in routes. This is all merged into one pass so I + // don't also accidentally escape `-` and make it harder to detect it to + // ban it from template parameters. + /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, + function(m, key, extra) { + if (key == null) return "\\" + m + keys.push({k: key, r: extra === "..."}) + if (extra === "...") return "(.*)" + if (extra === ".") return "([^/]+)\\." + return "([^/]+)" + (extra || "") + } + ) + "$") + return function(data) { + // First, check the params. Usually, there isn't any, and it's just + // checking a static set. + for (var i = 0; i < templateKeys.length; i++) { + if (templateData.params[templateKeys[i]] !== data.params[templateKeys[i]]) return false + } + // If no interpolations exist, let's skip all the ceremony + if (!keys.length) return regexp.test(data.path) + var values = regexp.exec(data.path) + if (values == null) return false + for (var i = 0; i < keys.length; i++) { + data.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]) + } + return true + } +} diff --git a/pathname/parse.js b/pathname/parse.js new file mode 100644 index 00000000..b68fd6d8 --- /dev/null +++ b/pathname/parse.js @@ -0,0 +1,24 @@ +"use strict" + +var parseQueryString = require("../querystring/parse") + +// Returns `{path, params}` from `url` +module.exports = function(url) { + var queryIndex = url.indexOf("?") + var hashIndex = url.indexOf("#") + var queryEnd = hashIndex < 0 ? url.length : hashIndex + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex + var path = url.slice(0, pathEnd).replace(/\/{2,}/g, "/") + var params = {} + + if (!path) path = "/" + else { + if (path[0] !== "/") path = "/" + path + if (path.length > 1 && path[path.length - 1] === "/") path = path.slice(0, -1) + } + // Note: these are reversed because `parseQueryString` appends parameters + // only if they don't exist. Please don't flip them. + if (queryIndex >= 0) parseQueryString(url.slice(queryIndex + 1, queryEnd), params) + if (hashIndex >= 0) parseQueryString(url.slice(hashIndex + 1), params) + return {path: path, params: params} +} diff --git a/pathname/tests/index.html b/pathname/tests/index.html new file mode 100644 index 00000000..659d8eec --- /dev/null +++ b/pathname/tests/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/pathname/tests/test-buildPathname.js b/pathname/tests/test-buildPathname.js new file mode 100644 index 00000000..1d6fc3f1 --- /dev/null +++ b/pathname/tests/test-buildPathname.js @@ -0,0 +1,109 @@ +"use strict" + +var o = require("../../ospec/ospec") +var buildPathname = require("../../pathname/build") + +o.spec("buildPathname", function() { + function test(prefix) { + o("returns path if no params", function () { + var string = buildPathname(prefix + "/route/foo", undefined) + + o(string).equals(prefix + "/route/foo") + }) + o("skips interpolation if no params", function () { + var string = buildPathname(prefix + "/route/:id", undefined) + + o(string).equals(prefix + "/route/:id") + }) + o("appends query strings", function () { + var string = buildPathname(prefix + "/route/foo", {a: "b", c: 1}) + + o(string).equals(prefix + "/route/foo?a=b&c=1") + }) + o("inserts template parameters at end", function () { + var string = buildPathname(prefix + "/route/:id", {id: "1"}) + + o(string).equals(prefix + "/route/1") + }) + o("inserts template parameters at beginning", function () { + var string = buildPathname(prefix + "/:id/foo", {id: "1"}) + + o(string).equals(prefix + "/1/foo") + }) + o("inserts template parameters at middle", function () { + var string = buildPathname(prefix + "/route/:id/foo", {id: "1"}) + + o(string).equals(prefix + "/route/1/foo") + }) + o("inserts variadic paths", function () { + var string = buildPathname(prefix + "/route/:foo...", {foo: "id/1"}) + + o(string).equals(prefix + "/route/id/1") + }) + o("inserts variadic paths with initial slashes", function () { + var string = buildPathname(prefix + "/route/:foo...", {foo: "/id/1"}) + + o(string).equals(prefix + "/route//id/1") + }) + o("skips template parameters at end if param missing", function () { + var string = buildPathname(prefix + "/route/:id", {param: 1}) + + o(string).equals(prefix + "/route/:id?param=1") + }) + o("skips template parameters at beginning if param missing", function () { + var string = buildPathname(prefix + "/:id/foo", {param: 1}) + + o(string).equals(prefix + "/:id/foo?param=1") + }) + o("skips template parameters at middle if param missing", function () { + var string = buildPathname(prefix + "/route/:id/foo", {param: 1}) + + o(string).equals(prefix + "/route/:id/foo?param=1") + }) + o("skips variadic template parameters if param missing", function () { + var string = buildPathname(prefix + "/route/:foo...", {param: "/id/1"}) + + o(string).equals(prefix + "/route/:foo...?param=%2Fid%2F1") + }) + o("handles escaped values", function() { + var data = buildPathname(prefix + "/route/:foo", {"foo": ";:@&=+$,/?%#"}) + + o(data).equals(prefix + "/route/%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23") + }) + o("handles unicode", function() { + var data = buildPathname(prefix + "/route/:รถ", {"รถ": "รถ"}) + + o(data).equals(prefix + "/route/%C3%B6") + }) + o("handles zero", function() { + var string = buildPathname(prefix + "/route/:a", {a: 0}) + + o(string).equals(prefix + "/route/0") + }) + o("handles false", function() { + var string = buildPathname(prefix + "/route/:a", {a: false}) + + o(string).equals(prefix + "/route/false") + }) + o("handles dashes", function() { + var string = buildPathname(prefix + "/:lang-:region/route", { + lang: "en", + region: "US" + }) + + o(string).equals(prefix + "/en-US/route") + }) + o("handles dots", function() { + var string = buildPathname(prefix + "/:file.:ext/view", { + file: "image", + ext: "png" + }) + + o(string).equals(prefix + "/image.png/view") + }) + } + o.spec("absolute", function() { test("") }) + o.spec("relative", function() { test("..") }) + o.spec("absolute + domain", function() { test("https://example.com") }) + o.spec("absolute + `file:`", function() { test("file://") }) +}) diff --git a/pathname/tests/test-compileTemplate.js b/pathname/tests/test-compileTemplate.js new file mode 100644 index 00000000..5f7a5f9c --- /dev/null +++ b/pathname/tests/test-compileTemplate.js @@ -0,0 +1,233 @@ +"use strict" + +var o = require("../../ospec/ospec") +var parsePathname = require("../../pathname/parse") +var compileTemplate = require("../../pathname/compileTemplate") + +o.spec("compileTemplate", function() { + o("checks empty string", function() { + var data = parsePathname("/") + o(compileTemplate("/")(data)).equals(true) + o(data.params).deepEquals({}) + }) + o("checks identical match", function() { + var data = parsePathname("/foo") + o(compileTemplate("/foo")(data)).equals(true) + o(data.params).deepEquals({}) + }) + o("checks identical mismatch", function() { + var data = parsePathname("/bar") + o(compileTemplate("/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks single parameter", function() { + var data = parsePathname("/1") + o(compileTemplate("/:id")(data)).equals(true) + o(data.params).deepEquals({id: "1"}) + }) + o("checks single variadic parameter", function() { + var data = parsePathname("/some/path") + o(compileTemplate("/:id...")(data)).equals(true) + o(data.params).deepEquals({id: "some/path"}) + }) + o("checks single parameter with extra match", function() { + var data = parsePathname("/1/foo") + o(compileTemplate("/:id/foo")(data)).equals(true) + o(data.params).deepEquals({id: "1"}) + }) + o("checks single parameter with extra mismatch", function() { + var data = parsePathname("/1/bar") + o(compileTemplate("/:id/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks single variadic parameter with extra match", function() { + var data = parsePathname("/some/path/foo") + o(compileTemplate("/:id.../foo")(data)).equals(true) + o(data.params).deepEquals({id: "some/path"}) + }) + o("checks single variadic parameter with extra mismatch", function() { + var data = parsePathname("/some/path/bar") + o(compileTemplate("/:id.../foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters", function() { + var data = parsePathname("/1/2") + o(compileTemplate("/:id/:name")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks incomplete multiple parameters", function() { + var data = parsePathname("/1") + o(compileTemplate("/:id/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters with extra match", function() { + var data = parsePathname("/1/2/foo") + o(compileTemplate("/:id/:name/foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks multiple parameters with extra mismatch", function() { + var data = parsePathname("/1/2/bar") + o(compileTemplate("/:id/:name/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters, last variadic, with extra match", function() { + var data = parsePathname("/1/some/path/foo") + o(compileTemplate("/:id/:name.../foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "some/path"}) + }) + o("checks multiple parameters, last variadic, with extra mismatch", function() { + var data = parsePathname("/1/some/path/bar") + o(compileTemplate("/:id/:name.../foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters", function() { + var data = parsePathname("/1/sep/2") + o(compileTemplate("/:id/sep/:name")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks incomplete multiple separated parameters", function() { + var data = parsePathname("/1") + o(compileTemplate("/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + data = parsePathname("/1/sep") + o(compileTemplate("/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters missing sep", function() { + var data = parsePathname("/1/2") + o(compileTemplate("/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters with extra match", function() { + var data = parsePathname("/1/sep/2/foo") + o(compileTemplate("/:id/sep/:name/foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks multiple separated parameters with extra mismatch", function() { + var data = parsePathname("/1/sep/2/bar") + o(compileTemplate("/:id/sep/:name/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters, last variadic, with extra match", function() { + var data = parsePathname("/1/sep/some/path/foo") + o(compileTemplate("/:id/sep/:name.../foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "some/path"}) + }) + o("checks multiple separated parameters, last variadic, with extra mismatch", function() { + var data = parsePathname("/1/sep/some/path/bar") + o(compileTemplate("/:id/sep/:name.../foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters + prefix", function() { + var data = parsePathname("/route/1/2") + o(compileTemplate("/route/:id/:name")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks incomplete multiple parameters + prefix", function() { + var data = parsePathname("/route/1") + o(compileTemplate("/route/:id/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters + prefix with extra match", function() { + var data = parsePathname("/route/1/2/foo") + o(compileTemplate("/route/:id/:name/foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks multiple parameters + prefix with extra mismatch", function() { + var data = parsePathname("/route/1/2/bar") + o(compileTemplate("/route/:id/:name/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple parameters + prefix, last variadic, with extra match", function() { + var data = parsePathname("/route/1/some/path/foo") + o(compileTemplate("/route/:id/:name.../foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "some/path"}) + }) + o("checks multiple parameters + prefix, last variadic, with extra mismatch", function() { + var data = parsePathname("/route/1/some/path/bar") + o(compileTemplate("/route/:id/:name.../foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters + prefix", function() { + var data = parsePathname("/route/1/sep/2") + o(compileTemplate("/route/:id/sep/:name")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks incomplete multiple separated parameters + prefix", function() { + var data = parsePathname("/route/1") + o(compileTemplate("/route/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + var data = parsePathname("/route/1/sep") + o(compileTemplate("/route/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters + prefix missing sep", function() { + var data = parsePathname("/route/1/2") + o(compileTemplate("/route/:id/sep/:name")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters + prefix with extra match", function() { + var data = parsePathname("/route/1/sep/2/foo") + o(compileTemplate("/route/:id/sep/:name/foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "2"}) + }) + o("checks multiple separated parameters + prefix with extra mismatch", function() { + var data = parsePathname("/route/1/sep/2/bar") + o(compileTemplate("/route/:id/sep/:name/foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks multiple separated parameters + prefix, last variadic, with extra match", function() { + var data = parsePathname("/route/1/sep/some/path/foo") + o(compileTemplate("/route/:id/sep/:name.../foo")(data)).equals(true) + o(data.params).deepEquals({id: "1", name: "some/path"}) + }) + o("checks multiple separated parameters + prefix, last variadic, with extra mismatch", function() { + var data = parsePathname("/route/1/sep/some/path/bar") + o(compileTemplate("/route/:id/sep/:name.../foo")(data)).equals(false) + o(data.params).deepEquals({}) + }) + o("checks query params match", function() { + var data = parsePathname("/route/1?foo=bar") + o(compileTemplate("/route/:id?foo=bar")(data)).equals(true) + o(data.params).deepEquals({id: "1", foo: "bar"}) + }) + o("checks query params mismatch", function() { + var data = parsePathname("/route/1?foo=bar") + o(compileTemplate("/route/:id?foo=1")(data)).equals(false) + o(data.params).deepEquals({foo: "bar"}) + o(compileTemplate("/route/:id?bar=foo")(data)).equals(false) + o(data.params).deepEquals({foo: "bar"}) + }) + o("checks hash params match", function() { + var data = parsePathname("/route/1#foo=bar") + o(compileTemplate("/route/:id#foo=bar")(data)).equals(true) + o(data.params).deepEquals({id: "1", foo: "bar"}) + }) + o("checks hash params mismatch", function() { + var data = parsePathname("/route/1#foo=bar") + o(compileTemplate("/route/:id#foo=1")(data)).equals(false) + o(data.params).deepEquals({foo: "bar"}) + o(compileTemplate("/route/:id#bar=foo")(data)).equals(false) + o(data.params).deepEquals({foo: "bar"}) + }) + o("checks dot before dot", function() { + var data = parsePathname("/file.test.png/edit") + o(compileTemplate("/:file.:ext/edit")(data)).equals(true) + o(data.params).deepEquals({file: "file.test", ext: "png"}) + }) + o("checks dash before dot", function() { + var data = parsePathname("/file-test.png/edit") + o(compileTemplate("/:file.:ext/edit")(data)).equals(true) + o(data.params).deepEquals({file: "file-test", ext: "png"}) + }) + o("checks dot before dash", function() { + var data = parsePathname("/file.test-png/edit") + o(compileTemplate("/:file-:ext/edit")(data)).equals(true) + o(data.params).deepEquals({file: "file.test", ext: "png"}) + }) + o("checks dash before dash", function() { + var data = parsePathname("/file-test-png/edit") + o(compileTemplate("/:file-:ext/edit")(data)).equals(true) + o(data.params).deepEquals({file: "file-test", ext: "png"}) + }) +}) diff --git a/pathname/tests/test-parsePathname.js b/pathname/tests/test-parsePathname.js new file mode 100644 index 00000000..85b3476d --- /dev/null +++ b/pathname/tests/test-parsePathname.js @@ -0,0 +1,126 @@ +"use strict" + +var o = require("../../ospec/ospec") +var parsePathname = require("../../pathname/parse") + +o.spec("parsePathname", function() { + o("parses empty string", function() { + var data = parsePathname("") + o(data).deepEquals({ + path: "/", + params: {} + }) + }) + o("parses query at start", function() { + var data = parsePathname("?a=b&c=d") + o(data).deepEquals({ + path: "/", + params: {a: "b", c: "d"} + }) + }) + o("parses hash at start", function() { + var data = parsePathname("#a=b&c=d") + o(data).deepEquals({ + path: "/", + params: {a: "b", c: "d"} + }) + }) + o("parses query + hash at start", function() { + var data = parsePathname("?a=1&b=2#c=3&d=4") + o(data).deepEquals({ + path: "/", + params: {a: "1", b: "2", c: "3", d: "4"} + }) + }) + o("parses root", function() { + var data = parsePathname("/") + o(data).deepEquals({ + path: "/", + params: {} + }) + }) + o("parses root + query at start", function() { + var data = parsePathname("/?a=b&c=d") + o(data).deepEquals({ + path: "/", + params: {a: "b", c: "d"} + }) + }) + o("parses root + hash at start", function() { + var data = parsePathname("/#a=b&c=d") + o(data).deepEquals({ + path: "/", + params: {a: "b", c: "d"} + }) + }) + o("parses root + query + hash at start", function() { + var data = parsePathname("/?a=1&b=2#c=3&d=4") + o(data).deepEquals({ + path: "/", + params: {a: "1", b: "2", c: "3", d: "4"} + }) + }) + o("parses route", function() { + var data = parsePathname("/route/foo") + o(data).deepEquals({ + path: "/route/foo", + params: {} + }) + }) + o("parses route + empty query", function() { + var data = parsePathname("/route/foo?") + o(data).deepEquals({ + path: "/route/foo", + params: {} + }) + }) + o("parses route + empty hash", function() { + var data = parsePathname("/route/foo?") + o(data).deepEquals({ + path: "/route/foo", + params: {} + }) + }) + o("parses route + empty query + empty hash", function() { + var data = parsePathname("/route/foo?#") + o(data).deepEquals({ + path: "/route/foo", + params: {} + }) + }) + o("parses route + query", function() { + var data = parsePathname("/route/foo?a=1&b=2") + o(data).deepEquals({ + path: "/route/foo", + params: {a: "1", b: "2"} + }) + }) + o("parses route + hash", function() { + var data = parsePathname("/route/foo?c=3&d=4") + o(data).deepEquals({ + path: "/route/foo", + params: {c: "3", d: "4"} + }) + }) + o("parses route + query + hash", function() { + var data = parsePathname("/route/foo?a=1&b=2#c=3&d=4") + o(data).deepEquals({ + path: "/route/foo", + params: {a: "1", b: "2", c: "3", d: "4"} + }) + }) + o("deduplicates same-named params in query + hash", function() { + var data = parsePathname("/route/foo?a=1&b=2#a=3&c=4") + o(data).deepEquals({ + path: "/route/foo", + params: {a: "3", b: "2", c: "4"} + }) + }) + o("parses route + query + hash with lots of junk slashes", function() { + var data = parsePathname("//route/////foo//?a=1&b=2#c=3&d=4") + o(data).deepEquals({ + path: "/route/foo", + params: {a: "1", b: "2", c: "3", d: "4"} + }) + }) +}) diff --git a/querystring/parse.js b/querystring/parse.js index 287ec0da..3fcb60cc 100644 --- a/querystring/parse.js +++ b/querystring/parse.js @@ -1,10 +1,13 @@ "use strict" -module.exports = function(string) { +// The extra `data` parameter is for if you want to append to an existing +// parameters object. +module.exports = function(string, data) { + if (data == null) data = {} if (string === "" || string == null) return {} if (string.charAt(0) === "?") string = string.slice(1) - var entries = string.split("&"), data = {}, counters = {} + var entries = string.split("&"), counters = {} for (var i = 0; i < entries.length; i++) { var entry = entries[i].split("=") var key = decodeURIComponent(entry[0]) @@ -22,12 +25,13 @@ module.exports = function(string) { var isValue = j === levels.length - 1 if (level === "") { var key = levels.slice(0, j).join() - if (counters[key] == null) counters[key] = 0 + if (counters[key] == null) { + counters[key] = Array.isArray(cursor) ? cursor.length : 0 + } level = counters[key]++ } - if (cursor[level] == null) { - cursor[level] = isValue ? value : isNumber ? [] : {} - } + if (isValue) cursor[level] = value + else if (cursor[level] == null) cursor[level] = isNumber ? [] : {} cursor = cursor[level] } } diff --git a/querystring/tests/test-parseQueryString.js b/querystring/tests/test-parseQueryString.js index 343ac727..d34a06ee 100644 --- a/querystring/tests/test-parseQueryString.js +++ b/querystring/tests/test-parseQueryString.js @@ -93,4 +93,20 @@ o.spec("parseQueryString", function() { var data = parseQueryString("a") o(data).deepEquals({a: ""}) }) + o("prefers later values", function() { + var data = parseQueryString("a=1&b=2&a=3") + o(data).deepEquals({a: "3", b: "2"}) + }) + o("continues to append to arrays between calls", function() { + var data = {} + parseQueryString("a[]=1&a[]=2", data) + parseQueryString("a[]=3&a[]=4", data) + o(data).deepEquals({a: ["1", "2", "3", "4"]}) + }) + o("continues to append to objects between calls", function() { + var data = {} + parseQueryString("a[b]=1&a[c]=2", data) + parseQueryString("a[d]=3&a[e]=4", data) + o(data).deepEquals({a: {b: "1", c: "2", d: "3", e: "4"}}) + }) }) diff --git a/request/request.js b/request/request.js index d5980332..cc08d8d8 100644 --- a/request/request.js +++ b/request/request.js @@ -1,6 +1,6 @@ "use strict" -var buildQueryString = require("../querystring/build") +var buildPathname = require("../pathname/build") module.exports = function($window, Promise) { var callbackCount = 0 @@ -11,7 +11,7 @@ module.exports = function($window, Promise) { if (typeof url !== "string") { args = url; url = url.url } else if (args == null) args = {} var promise = new Promise(function(resolve, reject) { - factory(url, args, function (data) { + factory(buildPathname(url, args.params), args, function (data) { if (typeof args.type === "function") { if (Array.isArray(data)) { for (var i = 0; i < data.length; i++) { @@ -54,30 +54,11 @@ module.exports = function($window, Promise) { return false } - function interpolate(url, data, assemble) { - if (data == null) return url - url = url.replace(/:([^\/]+)/gi, function (m, key) { - return data[key] != null ? data[key] : m - }) - if (assemble && data != null) { - var querystring = buildQueryString(data) - if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring - } - return url - } - return { request: makeRequest(function(url, args, resolve, reject) { var method = args.method != null ? args.method.toUpperCase() : "GET" - var useBody = method !== "GET" && method !== "TRACE" && - (typeof args.useBody !== "boolean" || args.useBody) - - var data = args.data - var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) - if (useBody) { - if (typeof args.serialize === "function") data = args.serialize(data) - else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) - } + var body = args.body + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData) var xhr = new $window.XMLHttpRequest(), aborted = false, @@ -88,9 +69,9 @@ module.exports = function($window, Promise) { _abort.call(xhr) } - xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) + xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (assumeJSON && useBody && !hasHeader(args, /^content-type$/i)) { + if (assumeJSON && !hasHeader(args, /^content-type$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { @@ -98,7 +79,7 @@ module.exports = function($window, Promise) { } if (args.withCredentials) xhr.withCredentials = args.withCredentials if (args.timeout) xhr.timeout = args.timeout - if (args.responseType) xhr.responseType = args.responseType + xhr.responseType = args.responseType || (typeof args.extract === "function" ? "" : "json") for (var key in args.headers) { if ({}.hasOwnProperty.call(args.headers, key)) { @@ -115,19 +96,38 @@ module.exports = function($window, Promise) { if (xhr.readyState === 4) { try { var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) - var response = xhr.responseText + // When the response type isn't "" or "text", + // `xhr.responseText` is the wrong thing to use. + // Browsers do the right thing and throw here, and we + // should honor that and do the right thing by + // preferring `xhr.response` where possible/practical. + var response = xhr.response, message + + if (response == null) { + try { + response = xhr.responseText + // Note: this snippet is intentionally *after* + // `xhr.responseText` is accessed, since the + // above will throw in modern browsers (thus + // skipping the rest of this section). It's an + // IE hack to detect and work around the lack of + // native `responseType: "json"` support there. + if (typeof args.extract !== "function" && xhr.responseType === "json") response = JSON.parse(response) + } + catch (e) { response = null } + } + if (typeof args.extract === "function") { response = args.extract(xhr, args) success = true } else if (typeof args.deserialize === "function") { response = args.deserialize(response) - } else { - try {response = response ? JSON.parse(response) : null} - catch (e) {throw new Error("Invalid JSON: " + response)} } if (success) resolve(response) else { - var error = new Error(xhr.responseText) + try { message = xhr.responseText } + catch (e) { message = response } + var error = new Error(message) error.code = xhr.status error.response = response reject(error) @@ -139,23 +139,24 @@ module.exports = function($window, Promise) { } } - if (useBody && data != null) xhr.send(data) - else xhr.send() + if (body == null) xhr.send() + else if (typeof args.serialize === "function") xhr.send(args.serialize(body)) + else if (body instanceof $window.FormData) xhr.send(body) + else xhr.send(JSON.stringify(body)) }), jsonp: makeRequest(function(url, args, resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { + delete $window[callbackName] script.parentNode.removeChild(script) resolve(data) - delete $window[callbackName] } script.onerror = function() { + delete $window[callbackName] script.parentNode.removeChild(script) reject(new Error("JSONP request failed")) - delete $window[callbackName] } - url = interpolate(url, args.data, true) script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + encodeURIComponent(args.callbackKey || "callback") + "=" + encodeURIComponent(callbackName) diff --git a/request/tests/test-jsonp.js b/request/tests/test-jsonp.js index 076ab26e..58c7f08b 100644 --- a/request/tests/test-jsonp.js +++ b/request/tests/test-jsonp.js @@ -47,7 +47,7 @@ o.spec("jsonp", function() { return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify(queryData) + ")"} } }) - jsonp({url: "/item", data: {a: "b", c: "d"}}).then(function(data) { + jsonp({url: "/item", params: {a: "b", c: "d"}}).then(function(data) { delete data["callback"] o(data).deepEquals({a: "b", c: "d"}) }).then(done) diff --git a/request/tests/test-request.js b/request/tests/test-request.js index bb52f6b3..57aaa513 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -79,7 +79,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({a: request.query})} } }) - xhr({method: "GET", url: "/item", data: {x: "y"}}).then(function(data) { + xhr({method: "GET", url: "/item", params: {x: "y"}}).then(function(data) { o(data).deepEquals({a: "?x=y"}) }).then(done) }) @@ -89,7 +89,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({a: JSON.parse(request.body)})} } }) - xhr({method: "POST", url: "/item", data: {x: "y"}}).then(function(data) { + xhr({method: "POST", url: "/item", body: {x: "y"}}).then(function(data) { o(data).deepEquals({a: {x: "y"}}) }).then(done) }) @@ -99,7 +99,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({a: request.query})} } }) - xhr({method: "GET", url: "/item", data: {x: ":y"}}).then(function(data) { + xhr({method: "GET", url: "/item", params: {x: ":y"}}).then(function(data) { o(data).deepEquals({a: "?x=%3Ay"}) }).then(done) }) @@ -109,28 +109,88 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({a: JSON.parse(request.body)})} } }) - xhr({method: "POST", url: "/item", data: {x: ":y"}}).then(function(data) { + xhr({method: "POST", url: "/item", body: {x: ":y"}}).then(function(data) { o(data).deepEquals({a: {x: ":y"}}) }).then(done) }) o("works w/ parameterized url via GET", function(done) { mock.$defineRoutes({ "GET /item/y": function(request) { - return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query})} + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} } }) - xhr({method: "GET", url: "/item/:x", data: {x: "y"}}).then(function(data) { - o(data).deepEquals({a: "/item/y", b: "?x=y"}) + xhr({method: "GET", url: "/item/:x", params: {x: "y"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: {}, c: null}) }).then(done) }) o("works w/ parameterized url via POST", function(done) { mock.$defineRoutes({ "POST /item/y": function(request) { - return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})} + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} } }) - xhr({method: "POST", url: "/item/:x", data: {x: "y"}}).then(function(data) { - o(data).deepEquals({a: "/item/y", b: {x: "y"}}) + xhr({method: "POST", url: "/item/:x", params: {x: "y"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: {}, c: null}) + }).then(done) + }) + o("works w/ parameterized url + body via GET", function(done) { + mock.$defineRoutes({ + "GET /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} + } + }) + xhr({method: "GET", url: "/item/:x", params: {x: "y"}, body: {a: "b"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: {}, c: {a: "b"}}) + }).then(done) + }) + o("works w/ parameterized url + body via POST", function(done) { + mock.$defineRoutes({ + "POST /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} + } + }) + xhr({method: "POST", url: "/item/:x", params: {x: "y"}, body: {a: "b"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: {}, c: {a: "b"}}) + }).then(done) + }) + o("works w/ parameterized url + query via GET", function(done) { + mock.$defineRoutes({ + "GET /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} + } + }) + xhr({method: "GET", url: "/item/:x", params: {x: "y", q: "term"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: "?q=term", c: null}) + }).then(done) + }) + o("works w/ parameterized url + query via POST", function(done) { + mock.$defineRoutes({ + "POST /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: request.body})} + } + }) + xhr({method: "POST", url: "/item/:x", params: {x: "y", q: "term"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: "?q=term", c: null}) + }).then(done) + }) + o("works w/ parameterized url + query + body via GET", function(done) { + mock.$defineRoutes({ + "GET /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} + } + }) + xhr({method: "GET", url: "/item/:x", params: {x: "y", q: "term"}, body: {a: "b"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: "?q=term", c: {a: "b"}}) + }).then(done) + }) + o("works w/ parameterized url + query + body via POST", function(done) { + mock.$defineRoutes({ + "POST /item/y": function(request) { + return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query, c: JSON.parse(request.body)})} + } + }) + xhr({method: "POST", url: "/item/:x", params: {x: "y", q: "term"}, body: {a: "b"}}).then(function(data) { + o(data).deepEquals({a: "/item/y", b: "?q=term", c: {a: "b"}}) }).then(done) }) o("works w/ array", function(done) { @@ -139,7 +199,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})} } }) - xhr({method: "POST", url: "/items", data: [{x: "y"}]}).then(function(data) { + xhr({method: "POST", url: "/items", body: [{x: "y"}]}).then(function(data) { o(data).deepEquals({a: "/items", b: [{x: "y"}]}) }).then(done) }) @@ -201,7 +261,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({body: request.query})} } }) - xhr({method: "GET", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { + xhr({method: "GET", url: "/item", serialize: serialize, params: {id: 1}}).then(function(data) { o(data.body).equals("?id=1") }).then(done) }) @@ -215,7 +275,7 @@ o.spec("xhr", function() { return {status: 200, responseText: JSON.stringify({body: request.body})} } }) - xhr({method: "POST", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { + xhr({method: "POST", url: "/item", serialize: serialize, body: {id: 1}}).then(function(data) { o(data.body).equals("id=1") }).then(done) }) @@ -230,7 +290,7 @@ o.spec("xhr", function() { } }) xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) { - o(data).equals("{\"test\":123}") + o(data).deepEquals({test: 123}) }).then(done) }) o("deserialize parameter works in POST", function(done) { @@ -244,12 +304,12 @@ o.spec("xhr", function() { } }) xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) { - o(data).equals("{\"test\":123}") + o(data).deepEquals({test: 123}) }).then(done) }) o("extract parameter works in GET", function(done) { var extract = function() { - return JSON.stringify({test: 123}) + return {test: 123} } mock.$defineRoutes({ @@ -258,12 +318,12 @@ o.spec("xhr", function() { } }) xhr({method: "GET", url: "/item", extract: extract}).then(function(data) { - o(data).equals("{\"test\":123}") + o(data).deepEquals({test: 123}) }).then(done) }) o("extract parameter works in POST", function(done) { var extract = function() { - return JSON.stringify({test: 123}) + return {test: 123} } mock.$defineRoutes({ @@ -272,7 +332,7 @@ o.spec("xhr", function() { } }) xhr({method: "POST", url: "/item", extract: extract}).then(function(data) { - o(data).equals("{\"test\":123}") + o(data).deepEquals({test: 123}) }).then(done) }) o("ignores deserialize if extract is defined", function(done) { @@ -485,7 +545,8 @@ o.spec("xhr", function() { }) xhr({method: "GET", url: "/item"}).catch(function(e) { o(e instanceof Error).equals(true) - o(e.message).equals(JSON.stringify({error: "error"})) + o(e.message).equals("[object Object]") + o(e.response).deepEquals({error: "error"}) o(e.code).equals(500) }).then(done) }) @@ -508,7 +569,8 @@ o.spec("xhr", function() { } }) xhr({method: "GET", url: "/item"}).catch(function(e) { - o(e.message).equals("Invalid JSON: error") + o(e.message).equals("null") + o(e.response).equals(null) }).then(done) }) o("triggers all branched catches upon rejection", function(done) { diff --git a/router/router.js b/router/router.js index 106f6863..58af7f4e 100644 --- a/router/router.js +++ b/router/router.js @@ -1,7 +1,9 @@ "use strict" -var buildQueryString = require("../querystring/build") -var parseQueryString = require("../querystring/parse") +var buildPathname = require("../pathname/build") +var parsePathname = require("../pathname/parse") +var compileTemplate = require("../pathname/compileTemplate") +var assign = require("../pathname/assign") module.exports = function($window) { var supportsPushState = typeof $window.history.pushState === "function" @@ -14,58 +16,15 @@ module.exports = function($window) { } var asyncId - function debounceAsync(callback) { - return function() { - if (asyncId != null) return - asyncId = callAsync(function() { - asyncId = null - callback() - }) - } - } - - function parsePath(path, queryData, hashData) { - var queryIndex = path.indexOf("?") - var hashIndex = path.indexOf("#") - var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length - if (queryIndex > -1) { - var queryEnd = hashIndex > -1 ? hashIndex : path.length - var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) - for (var key in queryParams) queryData[key] = queryParams[key] - } - if (hashIndex > -1) { - var hashParams = parseQueryString(path.slice(hashIndex + 1)) - for (var key in hashParams) hashData[key] = hashParams[key] - } - return path.slice(0, pathEnd) - } - var router = {prefix: "#!"} router.getPath = function() { - var type = router.prefix.charAt(0) - switch (type) { - case "#": return normalize("hash").slice(router.prefix.length) - case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") - default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") - } + if (router.prefix.charAt(0) === "#") return normalize("hash").slice(router.prefix.length) + if (router.prefix.charAt(0) === "?") return normalize("search").slice(router.prefix.length) + normalize("hash") + return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") } + router.setPath = function(path, data, options) { - var queryData = {}, hashData = {} - path = parsePath(path, queryData, hashData) - if (data != null) { - for (var key in data) queryData[key] = data[key] - path = path.replace(/:([^\/]+)/g, function(match, token) { - delete queryData[token] - return data[token] - }) - } - - var query = buildQueryString(queryData) - if (query) path += "?" + query - - var hash = buildQueryString(hashData) - if (hash) path += "#" + hash - + path = buildPathname(path, data) if (supportsPushState) { var state = options ? options.state : null var title = options ? options.title : null @@ -75,36 +34,53 @@ module.exports = function($window) { } else $window.location.href = router.prefix + path } - router.defineRoutes = function(routes, resolve, reject) { + + router.defineRoutes = function(routes, resolve, reject, defaultRoute) { + var compiled = Object.keys(routes).map(function(route) { + if (route.charAt(0) !== "/") throw new SyntaxError("Routes must start with a `/`") + if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) { + throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`") + } + return { + route: route, + component: routes[route], + check: compileTemplate(route), + } + }) + + if (defaultRoute != null) { + var defaultData = parsePathname(defaultRoute) + + if (!compiled.some(function (i) { return i.check(defaultData) })) { + throw new ReferenceError("Default route doesn't match any known routes") + } + } + function resolveRoute() { var path = router.getPath() - var params = {} - var pathname = parsePath(path, params, params) + var data = parsePathname(path) - var state = $window.history.state - if (state != null) { - for (var k in state) params[k] = state[k] - } - for (var route in routes) { - var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") + assign(data.params, $window.history.state) - if (matcher.test(pathname)) { - pathname.replace(matcher, function() { - var keys = route.match(/:[^\/]+/g) || [] - var values = [].slice.call(arguments, 1, -2) - for (var i = 0; i < keys.length; i++) { - params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) - } - resolve(routes[route], params, path, route) - }) + for (var i = 0; i < compiled.length; i++) { + if (compiled[i].check(data)) { + resolve(compiled[i].component, data.params, path, compiled[i].route) return } } - reject(path, params) + reject(path, data.params) } - if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) + if (supportsPushState) { + $window.onpopstate = function() { + if (asyncId) return + asyncId = callAsync(function() { + asyncId = null + resolveRoute() + }) + } + } else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() } diff --git a/router/tests/test-defineRoutes.js b/router/tests/test-defineRoutes.js index 51b3cc09..414b6244 100644 --- a/router/tests/test-defineRoutes.js +++ b/router/tests/test-defineRoutes.js @@ -22,14 +22,14 @@ o.spec("Router.defineRoutes", function() { o("calls onRouteChange on init", function(done) { $window.location.href = prefix + "/a" router.defineRoutes({"/a": {data: 1}}, onRouteChange, onFail) - + callAsync(function() { o(onRouteChange.callCount).equals(1) - + done() }) }) - + o("resolves to route", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) @@ -38,7 +38,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -51,7 +51,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {"รถ": "รถ"}, "/รถ?รถ=รถ#รถ=รถ", "/รถ"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -64,7 +64,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {"รถ": "รถ"}, "/รถ?รถ=รถ#รถ=รถ", "/รถ"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -81,7 +81,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -94,7 +94,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x"}, "/test/x", "/test/:a"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -107,7 +107,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x", b: "y"}, "/test/x/y", "/test/:a/:b"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -120,7 +120,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x/y"}, "/test/x/y", "/test/:a..."]) o(onFail.callCount).equals(0) - + done() }) }) @@ -133,7 +133,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b&c=d", "/test"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -146,7 +146,7 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test#a=b&c=d", "/test"]) o(onFail.callCount).equals(0) - + done() }) }) @@ -159,7 +159,20 @@ o.spec("Router.defineRoutes", function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b#c=d", "/test"]) o(onFail.callCount).equals(0) - + + done() + }) + }) + + o("handles route with search and hash + duplicate params", function(done) { + $window.location.href = prefix + "/test?a=b#a=d" + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) + + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "d"}, "/test?a=b#a=d", "/test"]) + o(onFail.callCount).equals(0) + done() }) }) @@ -171,7 +184,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onFail.callCount).equals(1) o(onFail.args).deepEquals(["/test", {}]) - + done() }) }) @@ -183,7 +196,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onFail.callCount).equals(1) o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) - + done() }) }) @@ -195,7 +208,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) - + done() }) }) @@ -207,7 +220,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) - + done() }) }) @@ -223,7 +236,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) - + done() }) }) @@ -239,7 +252,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) - + done() }) }) @@ -254,7 +267,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) - + done() }) }) @@ -269,7 +282,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) - + done() }) }) @@ -280,7 +293,7 @@ o.spec("Router.defineRoutes", function() { callAsync(function() { o(onRouteChange.callCount).equals(1) - + done() }) }) diff --git a/router/tests/test-setPath.js b/router/tests/test-setPath.js index d18c7525..a76d3580 100644 --- a/router/tests/test-setPath.js +++ b/router/tests/test-setPath.js @@ -55,7 +55,7 @@ o.spec("Router.setPath", function() { router.setPath("/other/x/y/z?c=d#e=f") o(router.getPath()).equals("/other/x/y/z?c=d#e=f") - + done() }) }) @@ -67,7 +67,7 @@ o.spec("Router.setPath", function() { router.setPath("/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6") o(router.getPath()).equals("/รถ?รถ=รถ#รถ=รถ") - + done() }) }) @@ -79,7 +79,7 @@ o.spec("Router.setPath", function() { router.setPath("/รถ?รถ=รถ#รถ=รถ") o(router.getPath()).equals("/รถ?รถ=รถ#รถ=รถ") - + done() }) }) @@ -96,7 +96,7 @@ o.spec("Router.setPath", function() { router.setPath("/other/x/y/z?c=d#e=f") o(router.getPath()).equals("/other/x/y/z?c=d#e=f") - + done() }) }) @@ -109,7 +109,7 @@ o.spec("Router.setPath", function() { $window.onpopstate() o(router.getPath()).equals("/other/x/y/z?c=d#e=f") - + done() }) }) @@ -120,8 +120,8 @@ o.spec("Router.setPath", function() { callAsync(function() { router.setPath("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"}) - o(router.getPath()).equals("/other/x/y/z?c=d&e=f") - + o(router.getPath()).equals("/other/x/y%2Fz?c=d&e=f") + done() }) }) @@ -134,7 +134,7 @@ o.spec("Router.setPath", function() { $window.history.back() o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + "/") - + done() }) }) @@ -149,7 +149,7 @@ o.spec("Router.setPath", function() { var slash = prefix[0] === "/" ? "" : "/" o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test") - + done() }) }) @@ -161,7 +161,7 @@ o.spec("Router.setPath", function() { router.setPath("/other", null, {state: {a: 1}}) o($window.history.state).deepEquals({a: 1}) - + done() }) }) diff --git a/stream/change-log.md b/stream/change-log.md index a7c5bf7b..7cd494a0 100644 --- a/stream/change-log.md +++ b/stream/change-log.md @@ -1,9 +1,17 @@ # Change log for stream -## 2.0.0 -- renamed HALT to SKIP [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) -- rewrote implementation [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) -- stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150) +- [Upcoming](#upcoming) +- [v2.0.0](#v200) +- [v1.1.0](#v110) -## 1.1.0 -- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) +### Upcoming... + +### 2.0.0 +- when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200), [#2369](https://github.com/MithrilJS/mithril.js/pull/2369)) +- Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` ([#1944](https://github.com/MithrilJS/mithril.js/issues/1944)) +- renamed HALT to SKIP ([#2207](https://github.com/MithrilJS/mithril.js/pull/2207)) +- rewrote implementation ([#2207](https://github.com/MithrilJS/mithril.js/pull/2207)) +- Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150) + +### 1.1.0 +- Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) diff --git a/stream/package.json b/stream/package.json index d78616b6..7a9d6942 100644 --- a/stream/package.json +++ b/stream/package.json @@ -1,9 +1,8 @@ { "name": "mithril-stream", - "version": "1.1.0", + "version": "2.0.0", "description": "Streaming data, mithril-style", "main": "stream.js", - "module": "stream.mjs", "directories": { "test": "tests" }, diff --git a/stream/stream.js b/stream/stream.js index bc5d58f5..bc914723 100644 --- a/stream/stream.js +++ b/stream/stream.js @@ -24,11 +24,13 @@ function Stream(value) { var dependentFns = [] function stream(v) { - if (arguments.length && v !== Stream.SKIP && open(stream)) { + if (arguments.length && v !== Stream.SKIP) { value = v - stream.changing() - stream.state = "active" - dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) + if (open(stream)) { + stream.changing() + stream.state = "active" + dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) + } } return value @@ -36,11 +38,11 @@ function Stream(value) { stream.constructor = Stream stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" + stream.parents = [] stream.changing = function() { open(stream) && (stream.state = "changing") dependentStreams.forEach(function(s) { - s.dependent && s.dependent.changing() s.changing() }) } @@ -49,6 +51,7 @@ function Stream(value) { var target = stream.state === "active" && ignoreInitial !== Stream.SKIP ? Stream(fn(value)) : Stream() + target.parents.push(stream) dependentStreams.push(target) dependentFns.push(fn) @@ -60,8 +63,9 @@ function Stream(value) { end = Stream() end.map(function(value) { if (value === true) { + stream.parents.forEach(function (p) {p.unregisterChild(stream)}) stream.state = "ended" - dependentStreams.length = dependentFns.length = 0 + stream.parents.length = dependentStreams.length = dependentFns.length = 0 } return value }) @@ -73,6 +77,14 @@ function Stream(value) { stream["fantasy-land/map"] = stream.map stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) } + stream.unregisterChild = function(child) { + var childIndex = dependentStreams.indexOf(child) + if (childIndex !== -1) { + dependentStreams.splice(childIndex, 1) + dependentFns.splice(childIndex, 1) + } + } + Object.defineProperty(stream, "end", { get: function() { return end || createEnd() } }) @@ -92,8 +104,8 @@ function combine(fn, streams) { var changed = [] - streams.forEach(function(s) { - s.map(function(value) { + var mappers = streams.map(function(s) { + return s.map(function(value) { changed.push(s) if (ready || streams.every(function(s) { return s.state !== "pending" })) { ready = true @@ -101,7 +113,15 @@ function combine(fn, streams) { changed = [] } return value - }, Stream.SKIP).parent = stream + }, Stream.SKIP) + }) + + var endStream = stream.end.map(function(value) { + if (value === true) { + mappers.forEach(function(mapper) { mapper.end(true) }) + endStream.end(true) + } + return undefined }) return stream diff --git a/stream/stream.mjs b/stream/stream.mjs deleted file mode 100644 index f67e8438..00000000 --- a/stream/stream.mjs +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-enable */ -Stream.SKIP = {} -Stream.lift = lift -Stream.scan = scan -Stream.merge = merge -Stream.combine = combine -Stream.scanMerge = scanMerge -Stream["fantasy-land/of"] = Stream - -var warnedHalt = false -Object.defineProperty(Stream, "HALT", { - get: function() { - warnedHalt || console.log("HALT is deprecated and has been renamed to SKIP"); - warnedHalt = true - return Stream.SKIP - } -}) - -function Stream(value) { - var dependentStreams = [] - var dependentFns = [] - - function stream(v) { - if (arguments.length && v !== Stream.SKIP && open(stream)) { - value = v - stream.changing() - stream.state = "active" - dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) - } - - return value - } - - stream.constructor = Stream - stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" - - stream.changing = function() { - open(stream) && (stream.state = "changing") - dependentStreams.forEach(function(s) { - s.dependent && s.dependent.changing() - s.changing() - }) - } - - stream.map = function(fn, ignoreInitial) { - var target = stream.state === "active" && ignoreInitial !== Stream.SKIP - ? Stream(fn(value)) - : Stream() - - dependentStreams.push(target) - dependentFns.push(fn) - return target - } - - var end - function createEnd() { - end = Stream() - end.map(function(value) { - if (value === true) { - stream.state = "ended" - dependentStreams.length = dependentFns.length = 0 - } - return value - }) - return end - } - - stream.toJSON = function() { return value != null && typeof value.toJSON === "function" ? value.toJSON() : value } - - stream["fantasy-land/map"] = stream.map - stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) } - - Object.defineProperty(stream, "end", { - get: function() { return end || createEnd() } - }) - - return stream -} - -function combine(fn, streams) { - var ready = streams.every(function(s) { - if (s.constructor !== Stream) - throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream") - return s.state === "active" - }) - var stream = ready - ? Stream(fn.apply(null, streams.concat([streams]))) - : Stream() - - var changed = [] - - streams.forEach(function(s) { - s.map(function(value) { - changed.push(s) - if (ready || streams.every(function(s) { return s.state !== "pending" })) { - ready = true - stream(fn.apply(null, streams.concat([changed]))) - changed = [] - } - return value - }, Stream.SKIP).parent = stream - }) - - return stream -} - -function merge(streams) { - return combine(function() { return streams.map(function(s) { return s() }) }, streams) -} - -function scan(fn, acc, origin) { - var stream = origin.map(function(v) { - var next = fn(acc, v) - if (next !== Stream.SKIP) acc = next - return next - }) - stream(acc) - return stream -} - -function scanMerge(tuples, seed) { - var streams = tuples.map(function(tuple) { return tuple[0] }) - - var stream = combine(function() { - var changed = arguments[arguments.length - 1] - streams.forEach(function(stream, i) { - if (changed.indexOf(stream) > -1) - seed = tuples[i][1](seed, stream()) - }) - - return seed - }, streams) - - stream(seed) - - return stream -} - -function lift() { - var fn = arguments[0] - var streams = Array.prototype.slice.call(arguments, 1) - return merge(streams).map(function(streams) { - return fn.apply(undefined, streams) - }) -} - -function open(s) { - return s.state === "pending" || s.state === "active" || s.state === "changing" -} - - -export default Stream \ No newline at end of file diff --git a/stream/tests/test-stream.js b/stream/tests/test-stream.js index 19295183..30f3a1f3 100644 --- a/stream/tests/test-stream.js +++ b/stream/tests/test-stream.js @@ -261,6 +261,15 @@ o.spec("stream", function() { o(thrown.constructor === TypeError).equals(false) o(spy.callCount).equals(0) }) + o("combine callback not called when child stream was ended", function () { + var spy = o.spy() + var a = Stream(1) + var b = Stream(2) + var mapped = Stream.combine(spy, [a, b]) + mapped.end(true) + a(11) + o(spy.callCount).equals(1) + }) }) o.spec("lift", function() { o("transforms value", function() { @@ -479,6 +488,12 @@ o.spec("stream", function() { o(spy.callCount).equals(1) }) + o("ended stream works like a container", function() { + var stream = Stream(1) + stream.end(true) + stream(2) + o(stream()).equals(2) + }) }) o.spec("toJSON", function() { o("works", function() { @@ -544,6 +559,14 @@ o.spec("stream", function() { o(stream["fantasy-land/map"]).equals(stream.map) }) + o("mapping function is not invoked after ending", function () { + var stream = Stream(undefined) + var fn = o.spy() + var mapped = stream.map(fn) + mapped.end(true) + stream(undefined) + o(fn.callCount).equals(1) + }) }) o.spec("ap", function() { o("works", function() { diff --git a/test-utils/xhrMock.js b/test-utils/xhrMock.js index 71c2f5b7..cd67d26c 100644 --- a/test-utils/xhrMock.js +++ b/test-utils/xhrMock.js @@ -43,13 +43,28 @@ module.exports = function() { args.user = user args.password = password } + this.responseType = "" + this.response = null + Object.defineProperty(this, "responseText", {get: function() { + if (this.responseType === "" || this.responseType === "text") { + return this.response + } else { + throw new Error("Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '" + this.responseType + "').") + } + }}) this.send = function(body) { var self = this if(!aborted) { var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) var data = handler({url: args.pathname, query: args.search || {}, body: body || null}) self.status = data.status - self.responseText = data.responseText + // Match spec + if (self.responseType === "json") { + try { self.response = JSON.parse(data.responseText) } + catch (e) { /* ignore */ } + } else { + self.response = data.responseText + } } else { self.status = 0 }