initial commit
This commit is contained in:
272
node_modules/page/History.md
generated
vendored
Normal file
272
node_modules/page/History.md
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
1.8.3 / 2018-01-22
|
||||
==================
|
||||
|
||||
This is a patch release which switches the build to rollup. Switching shaves 2k off of the gzipped size. In addition to this benefit, this also fixes a couple of small issues:
|
||||
|
||||
## Issues
|
||||
|
||||
* [#444] - Closure compiler warnings
|
||||
* [#458] - unsafe-eval error
|
||||
|
||||
[#444]: https://github.com/visionmedia/page.js/issues/444
|
||||
[#458]: https://github.com/visionmedia/page.js/issues/458
|
||||
|
||||
1.8.2 / 2018-01-22
|
||||
==================
|
||||
|
||||
This is a patch release fixing an issue that was meant to be solved in 1.8.1. page.js now runs in Node.js again, when there isn't a window environment.
|
||||
|
||||
1.8.1 / 2018-01-22
|
||||
==================
|
||||
|
||||
This is a patch release, fixing an issue with Node.js usage.
|
||||
|
||||
1.8.0 / 2018-01-17
|
||||
==================
|
||||
|
||||
This is a minor release, adding one new (minor) feature and a few bug fixes and documentation improvements.
|
||||
|
||||
## Controlling other pages
|
||||
|
||||
The new feature of this release is that page.js can now control other pages (windows) besides the main window. This makes it possible to control an iframe's routing. You can use it by passing the window option to start:
|
||||
|
||||
page('/', function(){
|
||||
// Whoa, I'm inside an iframe!
|
||||
});
|
||||
|
||||
page.start({ window: myiFrame.contentWindow });
|
||||
|
||||
Note that page is still a singleton, which means you can only have 1 page, so you can't control both an iframe and the main window at the same time.
|
||||
|
||||
This change was made to improve our testing, but in the future we'll make it possible to have multiple page objects so you really will be able to control multiple iframes and the main window all at the same time.
|
||||
|
||||
## Better hashbang support
|
||||
|
||||
Hashbang support has never been very good in page.js. But we're slowly improving it! In 1.8.0 we've fixed the problem where the app's full URL would show up in the hashbang upon start. http://example.com/my/page#!/my/page. Gross! No longer happens thanks to #447.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Those are the big things, but here's a list of all of the pull requests merged into 1.8.0:
|
||||
|
||||
* [#445] - Prevent hash from being part of the ctx.pathname
|
||||
* [#447] - Prevent going to the root when setting up the initial hashchange route
|
||||
* [#446] - Add a note about usage with a CDN
|
||||
* [#443] - Change test infrastructure to run inside of iframes
|
||||
* [#303] - page.exit callback example missing context param
|
||||
* [#267] - Added clarification for decoded plus signs in urls
|
||||
|
||||
[#445]: https://github.com/visionmedia/page.js/pull/4445
|
||||
[#447]: https://github.com/visionmedia/page.js/pull/4447
|
||||
[#446]: https://github.com/visionmedia/page.js/pull/4446
|
||||
[#443]: https://github.com/visionmedia/page.js/pull/4443
|
||||
[#303]: https://github.com/visionmedia/page.js/pull/4303
|
||||
[#267]: https://github.com/visionmedia/page.js/pull/4267
|
||||
|
||||
1.7.3 / 2018-01-15
|
||||
==================
|
||||
|
||||
This is a patch release making an improvement to how page.js works on pages that use the file protocol, such as Electron and nw.js apps.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
* [#441] - Set the page's base to be the current location when used under the file protocol with hashbang routing.
|
||||
|
||||
[#441]: https://github.com/visionmedia/page.js/pull/441
|
||||
|
||||
1.7.2 / 2018-01-15
|
||||
==================
|
||||
|
||||
Our first release in almost 2 years! This is a long overdue patch release that contains various bug fixes that have taken place over the course of the last couple of years. As releases will become much more often in the future (containing only a few fixes in most cases), I will be listing the closed issues in releases, but because there are 2 years worth it is not practical to do so in this release.
|
||||
|
||||
While you're here, if you haven't checked out page.js in a long time now is a great time. I've recently taken over maintenance and have a plan in place to take this great small library into the future. For now I am concentrating on stabilizing 1.x by fixing as many of the backlog of issues that have built up as I can. Once that is complete we'll start thinking about 2.0.
|
||||
|
||||
If you've submitted a PR here in the past and seen it be ignored, please come back! Your contributions are invaluable, and I promise that as long as I'm maintaining this project I'll do my best to always at least comment on pull requests and try to get them resolved.
|
||||
|
||||
That's all for now! Please report any issues you find in 1.7.2.
|
||||
|
||||
1.7.1 / 2016-03-17
|
||||
==================
|
||||
|
||||
* [#363] - Reinstate shadow DOM fixes which were reverted
|
||||
|
||||
[#363]: https://github.com/visionmedia/page.js/issues/363
|
||||
|
||||
1.7.0 / 2016-03-12
|
||||
==================
|
||||
|
||||
* [#284] - Use shadow dom when available ([@mwalid])
|
||||
* [#329] - Add type annotations for full closure-compiler advanced optimization support ([@chadkillingsworth])
|
||||
* [#328] - Include ctx in page.after example, fixes [#290]() ([@aaronshaf])
|
||||
|
||||
[#284]: https://github.com/visionmedia/page.js/issues/284
|
||||
[#329]: https://github.com/visionmedia/page.js/issues/329
|
||||
[#328]: https://github.com/visionmedia/page.js/issues/328
|
||||
[#290]: https://github.com/visionmedia/page.js/issues/290
|
||||
[@mwalid]: https://github.com/mwalid
|
||||
[@chadkillingsworth]: https://github.com/chadkillingsworth
|
||||
[@aaronshaf]: https://github.com/aaronshaf
|
||||
|
||||
1.6.4 / 2015-10-09
|
||||
==================
|
||||
|
||||
* fix wildcard route support (update path-to-regexp to v1.2.1)
|
||||
|
||||
1.6.3 / 2015-04-19
|
||||
==================
|
||||
|
||||
* fix including page.js on server side
|
||||
* fix including page.js if the document is already loaded
|
||||
* fix bug with click-event in Firefox
|
||||
|
||||
1.6.2 / 2015-03-06
|
||||
==================
|
||||
|
||||
* fix touch support #236
|
||||
* fix nw.js support #238
|
||||
* fix popstate issue in Safari #213
|
||||
|
||||
1.6.1 / 2015-02-16
|
||||
==================
|
||||
|
||||
* added `page.js` to npm files
|
||||
* back button works correct with hash links in Firefox #218
|
||||
|
||||
1.6.0./ 2015-01-27
|
||||
==================
|
||||
|
||||
* added `page.back` feature #157
|
||||
* added `decodeURLComponents` option #187
|
||||
* now `ctx.params` is object like in express #203
|
||||
* skip route processing if another one is called #172
|
||||
* docs improved
|
||||
* tests improved
|
||||
|
||||
|
||||
1.5.0 / 2014-11-29
|
||||
==================
|
||||
|
||||
* added page.exit(path, callback[, callback...])
|
||||
* added page.redirect(url)
|
||||
* fix: ignore links with `download` attribute
|
||||
* fix: remove URL encoding before parsing paths
|
||||
|
||||
1.4.1 / 2014-11-14
|
||||
==================
|
||||
|
||||
* fixed: hashbang navigation
|
||||
* added hashbang example
|
||||
* added tests
|
||||
|
||||
1.4.0 / 2014-11-12
|
||||
==================
|
||||
|
||||
* add hashbang support. Closes #112
|
||||
* add page.redirect() method
|
||||
* add plugins list to readme
|
||||
* add Context#handled option
|
||||
* Fix an issue where redirects in dispatch can be overwritten by ctx.save()
|
||||
* add support HTML5-History-API polyfill
|
||||
* make sameOrigin public
|
||||
* update path-to-regexp
|
||||
* allow for missing href in anchors.
|
||||
* update examples
|
||||
|
||||
|
||||
1.3.7 / 2013-09-09
|
||||
==================
|
||||
|
||||
* fix removal of fragment
|
||||
|
||||
1.3.6 / 2013-03-12
|
||||
==================
|
||||
|
||||
* fix links with target attribute
|
||||
|
||||
1.3.5 / 2013-02-12
|
||||
==================
|
||||
|
||||
* fix ctrl/cmd/shift clicks
|
||||
|
||||
1.3.4 / 2013-02-04
|
||||
==================
|
||||
|
||||
* add tmp .show() dispatch argument
|
||||
* add keywords to component.json
|
||||
|
||||
1.3.3 / 2012-12-14
|
||||
==================
|
||||
|
||||
* remove + support from path regexps
|
||||
|
||||
1.3.2 / 2012-11-26
|
||||
==================
|
||||
|
||||
* add explicit "#" check
|
||||
* add `window` to `addEventListener` calls
|
||||
|
||||
1.3.1 / 2012-09-21
|
||||
==================
|
||||
|
||||
* fix: onclick only when e.which == 1
|
||||
|
||||
1.3.0 / 2012-08-29
|
||||
==================
|
||||
|
||||
* add `page(fn)` support. Closes #27
|
||||
* add component.json
|
||||
* fix tests
|
||||
* fix examples
|
||||
|
||||
1.2.1 / 2012-08-02
|
||||
==================
|
||||
|
||||
* add transitions example
|
||||
* add exposing of `Context` and `Route` constructors
|
||||
* fix infinite loop issue unhandled paths containing query-strings
|
||||
|
||||
1.2.0 / 2012-07-05
|
||||
==================
|
||||
|
||||
* add `ctx.pathname`
|
||||
* add `ctx.querystring`
|
||||
* add support for passing a query-string through the dispatcher [ovaillancourt]
|
||||
* add `.defaultPrevented` support, ignoring page.js handling [ovaillancourt]
|
||||
|
||||
1.1.3 / 2012-06-18
|
||||
==================
|
||||
|
||||
* Added some basic client-side tests
|
||||
* Fixed initial dispatch in Firefox
|
||||
* Changed: no-op on subsequent `page()` calls. Closes #16
|
||||
|
||||
1.1.2 / 2012-06-13
|
||||
==================
|
||||
|
||||
* Fixed origin portno bug preventing :80 and :443 from working properly
|
||||
* Fixed: prevent cyclic refreshes. Closes #17
|
||||
|
||||
1.1.1 / 2012-06-11
|
||||
==================
|
||||
|
||||
* Added enterprisejs example
|
||||
* Added: join base for `.canonicalPath`. Closes #12
|
||||
* Fixed `location.origin` usage [fisch42]
|
||||
* Fixed `pushState()` when unhandled
|
||||
|
||||
1.1.0 / 2012-06-06
|
||||
==================
|
||||
|
||||
* Added `+` support to pathtoRegexp()
|
||||
* Added `page.base(path)` support
|
||||
* Added dispatch option to `page()`. Closes #10
|
||||
* Added `Context#originalPath`
|
||||
* Fixed unhandled links when .base is present. Closes #11
|
||||
* Fixed: `Context#path` to "/"
|
||||
|
||||
0.0.2 / 2012-06-05
|
||||
==================
|
||||
|
||||
* Added `make clean`
|
||||
* Added some mocha tests
|
||||
* Fixed: ignore fragments
|
||||
* Fixed: do not pushState on initial load
|
653
node_modules/page/Readme.md
generated
vendored
Normal file
653
node_modules/page/Readme.md
generated
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||

|
||||
|
||||
Tiny Express-inspired client-side router.
|
||||
|
||||
[](https://travis-ci.org/visionmedia/page.js)
|
||||
[](https://coveralls.io/r/visionmedia/page.js?branch=master)
|
||||
[](https://gitter.im/visionmedia/page.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
```js
|
||||
page('/', index)
|
||||
page('/user/:user', show)
|
||||
page('/user/:user/edit', edit)
|
||||
page('/user/:user/album', album)
|
||||
page('/user/:user/album/sort', sort)
|
||||
page('*', notfound)
|
||||
page()
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
There are multiple ways to install `page.js`.
|
||||
With package managers:
|
||||
|
||||
```bash
|
||||
$ npm install page # for browserify
|
||||
$ component install visionmedia/page.js
|
||||
$ bower install visionmedia/page.js
|
||||
```
|
||||
|
||||
Or use with a CDN. We support:
|
||||
|
||||
* [cdnjs](https://cdnjs.com/libraries/page.js)
|
||||
* [unpkg](https://unpkg.com/page/page.js)
|
||||
|
||||
Using with global script tags:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/page/page.js"></script>
|
||||
<script>
|
||||
page('/about', function(){
|
||||
// Do stuff
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Or with modules, in modern browsers:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import page from "//unpkg.com/page/page.mjs";
|
||||
|
||||
page('/home', () => { ... });
|
||||
</script>
|
||||
```
|
||||
|
||||
## Running examples
|
||||
|
||||
To run examples do the following to install dev dependencies and run the example server:
|
||||
|
||||
$ git clone git://github.com/visionmedia/page.js
|
||||
$ cd page.js
|
||||
$ npm install
|
||||
$ node examples
|
||||
$ open http://localhost:4000
|
||||
|
||||
Currently we have examples for:
|
||||
|
||||
- `basic` minimal application showing basic routing
|
||||
- `notfound` similar to `basic` with single-page 404 support
|
||||
- `album` showing pagination and external links
|
||||
- `profile` simple user profiles
|
||||
- `query-string` shows how you can integrate plugins using the router
|
||||
- `state` illustrates how the history state may be used to cache data
|
||||
- `server` illustrates how to use the dispatch option to server initial content
|
||||
- `chrome` Google Chrome style administration interface
|
||||
- `transitions` Shows off a simple technique for adding transitions between "pages"
|
||||
- `partials` using hogan.js to render mustache partials client side
|
||||
|
||||
__NOTE__: keep in mind these examples do not use jQuery or similar, so
|
||||
portions of the examples may be relatively verbose, though they're not
|
||||
directly related to page.js in any way.
|
||||
|
||||
## API
|
||||
|
||||
### page(path, callback[, callback ...])
|
||||
|
||||
Defines a route mapping `path` to the given `callback(s)`.
|
||||
Each callback is invoked with two arguments, [context](#context) and `next`. Much like Express invoking next will call the next registered callback with the given path.
|
||||
|
||||
```js
|
||||
page('/', user.list)
|
||||
page('/user/:id', user.load, user.show)
|
||||
page('/user/:id/edit', user.load, user.edit)
|
||||
page('*', notfound)
|
||||
```
|
||||
|
||||
Under certain conditions, links will be disregarded
|
||||
and will not be dispatched, such as:
|
||||
|
||||
- Links that are not of the same origin
|
||||
- Links with the `download` attribute
|
||||
- Links with the `target` attribute
|
||||
- Links with the `rel="external"` attribute
|
||||
|
||||
### page(callback)
|
||||
|
||||
This is equivalent to `page('*', callback)` for generic "middleware".
|
||||
|
||||
### page(path)
|
||||
|
||||
Navigate to the given `path`.
|
||||
|
||||
```js
|
||||
$('.view').click(function(e){
|
||||
page('/user/12')
|
||||
e.preventDefault()
|
||||
})
|
||||
```
|
||||
|
||||
### page(fromPath, toPath)
|
||||
|
||||
Setup redirect from one path to another.
|
||||
|
||||
### page.redirect(fromPath, toPath)
|
||||
|
||||
Identical to `page(fromPath, toPath)`
|
||||
|
||||
### page.redirect(path)
|
||||
Calling page.redirect with only a string as the first parameter
|
||||
redirects to another route.
|
||||
Waits for the current route to push state and after replaces it
|
||||
with the new one leaving the browser history clean.
|
||||
|
||||
```js
|
||||
page('/default', function(){
|
||||
// some logic to decide which route to redirect to
|
||||
if(admin) {
|
||||
page.redirect('/admin');
|
||||
} else {
|
||||
page.redirect('/guest');
|
||||
}
|
||||
});
|
||||
|
||||
page('/default');
|
||||
```
|
||||
|
||||
### page.show(path)
|
||||
|
||||
Identical to `page(path)` above.
|
||||
|
||||
### page([options])
|
||||
|
||||
Register page's `popstate` / `click` bindings. If you're
|
||||
doing selective binding you'll like want to pass `{ click: false }`
|
||||
to specify this yourself. The following options are available:
|
||||
|
||||
- `click` bind to click events [__true__]
|
||||
- `popstate` bind to popstate [__true__]
|
||||
- `dispatch` perform initial dispatch [__true__]
|
||||
- `hashbang` add `#!` before urls [__false__]
|
||||
- `decodeURLComponents` remove URL encoding from path components (query string, pathname, hash) [__true__]
|
||||
- `window` provide a window to control (by default it will control the main window)
|
||||
|
||||
If you wish to load serve initial content
|
||||
from the server you likely will want to
|
||||
set `dispatch` to __false__.
|
||||
|
||||
### page.start([options])
|
||||
|
||||
Identical to `page([options])` above.
|
||||
|
||||
### page.stop()
|
||||
|
||||
Unbind both the `popstate` and `click` handlers.
|
||||
|
||||
### page.base([path])
|
||||
|
||||
Get or set the base `path`. For example if page.js
|
||||
is operating within `/blog/*` set the base path to "/blog".
|
||||
|
||||
### page.strict([enable])
|
||||
|
||||
Get or set the strict path matching mode to `enable`. If enabled
|
||||
`/blog` will not match "/blog/" and `/blog/` will not match "/blog".
|
||||
|
||||
### page.exit(path, callback[, callback ...])
|
||||
|
||||
Defines an exit route mapping `path` to the given `callback(s)`.
|
||||
|
||||
Exit routes are called when a page changes, using the context
|
||||
from the previous change. For example:
|
||||
|
||||
```js
|
||||
page('/sidebar', function(ctx, next) {
|
||||
sidebar.open = true
|
||||
next()
|
||||
})
|
||||
|
||||
page.exit('/sidebar', function(ctx, next) {
|
||||
sidebar.open = false
|
||||
next()
|
||||
})
|
||||
```
|
||||
|
||||
### page.exit(callback)
|
||||
|
||||
Equivalent to `page.exit('*', callback)`.
|
||||
|
||||
### page.create([options])
|
||||
|
||||
Create a new page instance with the given options. Options provided
|
||||
are the same as provided in `page([options])` above. Use this if you need
|
||||
to control multiple windows (like iframes or popups) in addition
|
||||
to the main window.
|
||||
|
||||
```js
|
||||
var otherPage = page.create({ window: iframe.contentWindow });
|
||||
otherPage('/', main);
|
||||
```
|
||||
|
||||
### page.clickHandler
|
||||
|
||||
This is the click handler used by page to handle routing when a user clicks an anchor like `<a href="/user/profile">`. This is exported for those who want to disable the click handling behavior with `page.start({ click: false })`, but still might want to dispatch based on the click handler's logic in some scenarios.
|
||||
|
||||
### Context
|
||||
|
||||
Routes are passed `Context` objects, these may
|
||||
be used to share state, for example `ctx.user =`,
|
||||
as well as the history "state" `ctx.state` that
|
||||
the `pushState` API provides.
|
||||
|
||||
#### Context#save()
|
||||
|
||||
Saves the context using `replaceState()`. For example
|
||||
this is useful for caching HTML or other resources
|
||||
that were loaded for when a user presses "back".
|
||||
|
||||
#### Context#handled
|
||||
|
||||
If `true`, marks the context as handled to prevent [default 404 behaviour][404].
|
||||
For example this is useful for the routes with interminate quantity of the
|
||||
callbacks.
|
||||
|
||||
[404]: https://github.com/visionmedia/page.js#default-404-behaviour
|
||||
|
||||
#### Context#canonicalPath
|
||||
|
||||
Pathname including the "base" (if any) and query string "/admin/login?foo=bar".
|
||||
|
||||
#### Context#path
|
||||
|
||||
Pathname and query string "/login?foo=bar".
|
||||
|
||||
#### Context#querystring
|
||||
|
||||
Query string void of leading `?` such as "foo=bar", defaults to "".
|
||||
|
||||
#### Context#pathname
|
||||
|
||||
The pathname void of query string "/login".
|
||||
|
||||
#### Context#state
|
||||
|
||||
The `pushState` state object.
|
||||
|
||||
#### Context#title
|
||||
|
||||
The `pushState` title.
|
||||
|
||||
## Routing
|
||||
|
||||
The router uses the same string-to-regexp conversion
|
||||
that Express does, so things like ":id", ":id?", and "*" work
|
||||
as you might expect.
|
||||
|
||||
Another aspect that is much like Express is the ability to
|
||||
pass multiple callbacks. You can use this to your advantage
|
||||
to flatten nested callbacks, or simply to abstract components.
|
||||
|
||||
### Separating concerns
|
||||
|
||||
For example suppose you have a route to _edit_ users, and a
|
||||
route to _view_ users. In both cases you need to load the user.
|
||||
One way to achieve this is with several callbacks as shown here:
|
||||
|
||||
```js
|
||||
page('/user/:user', load, show)
|
||||
page('/user/:user/edit', load, edit)
|
||||
```
|
||||
|
||||
Using the `*` character we can alter this to match all
|
||||
routes prefixed with "/user" to achieve the same result:
|
||||
|
||||
```js
|
||||
page('/user/*', load)
|
||||
page('/user/:user', show)
|
||||
page('/user/:user/edit', edit)
|
||||
```
|
||||
|
||||
Likewise `*` can be used as catch-alls after all routes
|
||||
acting as a 404 handler, before all routes, in-between and
|
||||
so on. For example:
|
||||
|
||||
```js
|
||||
page('/user/:user', load, show)
|
||||
page('*', function(){
|
||||
$('body').text('Not found!')
|
||||
})
|
||||
```
|
||||
|
||||
### Default 404 behaviour
|
||||
|
||||
By default when a route is not matched,
|
||||
page.js invokes `page.stop()` to unbind
|
||||
itself, and proceed with redirecting to the
|
||||
location requested. This means you may use
|
||||
page.js with a multi-page application _without_
|
||||
explicitly binding to certain links.
|
||||
|
||||
### Working with parameters and contexts
|
||||
|
||||
Much like `request` and `response` objects are
|
||||
passed around in Express, page.js has a single
|
||||
"Context" object. Using the previous examples
|
||||
of `load` and `show` for a user, we can assign
|
||||
arbitrary properties to `ctx` to maintain state
|
||||
between callbacks.
|
||||
|
||||
To build a `load` function that will load
|
||||
the user for subsequent routes you'll need to
|
||||
access the ":id" passed. You can do this with
|
||||
`ctx.params.NAME` much like Express:
|
||||
|
||||
```js
|
||||
function load(ctx, next){
|
||||
var id = ctx.params.id
|
||||
}
|
||||
```
|
||||
|
||||
Then perform some kind of action against the server,
|
||||
assigning the user to `ctx.user` for other routes to
|
||||
utilize. `next()` is then invoked to pass control to
|
||||
the following matching route in sequence, if any.
|
||||
|
||||
```js
|
||||
function load(ctx, next){
|
||||
var id = ctx.params.id
|
||||
$.getJSON('/user/' + id + '.json', function(user){
|
||||
ctx.user = user
|
||||
next()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The "show" function might look something like this,
|
||||
however you may render templates or do anything you
|
||||
want. Note that here `next()` is _not_ invoked, because
|
||||
this is considered the "end point", and no routes
|
||||
will be matched until another link is clicked or
|
||||
`page(path)` is called.
|
||||
|
||||
```js
|
||||
function show(ctx){
|
||||
$('body')
|
||||
.empty()
|
||||
.append('<h1>' + ctx.user.name + '<h1>');
|
||||
}
|
||||
```
|
||||
|
||||
Finally using them like so:
|
||||
|
||||
```js
|
||||
page('/user/:id', load, show)
|
||||
```
|
||||
|
||||
**NOTE:** The value of `ctx.params.NAME` is decoded via `decodeURIComponent(sliceOfUrl)`. One exception though is the use of the plus sign (+) in the url, e.g. `/user/john+doe`, which is decoded to a space: `ctx.params.id == 'john doe'`. Also an encoded plus sign (`%2B`) is decoded to a space.
|
||||
|
||||
### Working with state
|
||||
|
||||
When working with the `pushState` API,
|
||||
and page.js you may optionally provide
|
||||
state objects available when the user navigates
|
||||
the history.
|
||||
|
||||
For example if you had a photo application
|
||||
and you performed a relatively extensive
|
||||
search to populate a list of images,
|
||||
normally when a user clicks "back" in
|
||||
the browser the route would be invoked
|
||||
and the query would be made yet-again.
|
||||
|
||||
An example implementation might look as follows:
|
||||
|
||||
```js
|
||||
function show(ctx){
|
||||
$.getJSON('/photos', function(images){
|
||||
displayImages(images)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You may utilize the history's state
|
||||
object to cache this result, or any
|
||||
other values you wish. This makes it
|
||||
possible to completely omit the query
|
||||
when a user presses back, providing
|
||||
a much nicer experience.
|
||||
|
||||
```js
|
||||
function show(ctx){
|
||||
if (ctx.state.images) {
|
||||
displayImages(ctx.state.images)
|
||||
} else {
|
||||
$.getJSON('/photos', function(images){
|
||||
ctx.state.images = images
|
||||
ctx.save()
|
||||
displayImages(images)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
__NOTE__: `ctx.save()` must be used
|
||||
if the state changes _after_ the first
|
||||
tick (xhr, setTimeout, etc), otherwise
|
||||
it is optional and the state will be
|
||||
saved after dispatching.
|
||||
|
||||
### Matching paths
|
||||
|
||||
Here are some examples of what's possible
|
||||
with the string to `RegExp` conversion.
|
||||
|
||||
Match an explicit path:
|
||||
|
||||
```js
|
||||
page('/about', callback)
|
||||
```
|
||||
|
||||
Match with required parameter accessed via `ctx.params.name`:
|
||||
|
||||
```js
|
||||
page('/user/:name', callback)
|
||||
```
|
||||
|
||||
Match with several params, for example `/user/tj/edit` or
|
||||
`/user/tj/view`.
|
||||
|
||||
```js
|
||||
page('/user/:name/:operation', callback)
|
||||
```
|
||||
|
||||
Match with one optional and one required, now `/user/tj`
|
||||
will match the same route as `/user/tj/show` etc:
|
||||
|
||||
```js
|
||||
page('/user/:name/:operation?', callback)
|
||||
```
|
||||
|
||||
Use the wildcard char `*` to match across segments,
|
||||
available via `ctx.params[N]` where __N__ is the
|
||||
index of `*` since you may use several. For example
|
||||
the following will match `/user/12/edit`, `/user/12/albums/2/admin`
|
||||
and so on.
|
||||
|
||||
```js
|
||||
page('/user/*', loadUser)
|
||||
```
|
||||
|
||||
Named wildcard accessed, for example `/file/javascripts/jquery.js`
|
||||
would provide "/javascripts/jquery.js" as `ctx.params.file`:
|
||||
|
||||
```js
|
||||
page('/file/:file(.*)', loadUser)
|
||||
```
|
||||
|
||||
And of course `RegExp` literals, where the capture
|
||||
groups are available via `ctx.params[N]` where __N__
|
||||
is the index of the capture group.
|
||||
|
||||
```js
|
||||
page(/^\/commits\/(\d+)\.\.(\d+)/, loadUser)
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
An example plugin _examples/query-string/query.js_
|
||||
demonstrates how to make plugins. It will provide a parsed `ctx.query` object
|
||||
derived from [node-querystring](https://github.com/visionmedia/node-querystring).
|
||||
|
||||
Usage by using "*" to match any path
|
||||
in order to parse the query-string:
|
||||
|
||||
```js
|
||||
page('*', parse)
|
||||
page('/', show)
|
||||
page()
|
||||
|
||||
function parse(ctx, next) {
|
||||
ctx.query = qs.parse(location.search.slice(1));
|
||||
next();
|
||||
}
|
||||
|
||||
function show(ctx) {
|
||||
if (Object.keys(ctx.query).length) {
|
||||
document
|
||||
.querySelector('pre')
|
||||
.textContent = JSON.stringify(ctx.query, null, 2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available plugins
|
||||
|
||||
- [querystring](https://github.com/visionmedia/page.js/blob/master/examples/query-string/query.js): provides a parsed `ctx.query` object derived from [node-querystring](https://github.com/visionmedia/node-querystring).
|
||||
- [body-parser](https://github.com/kethinov/page.js-body-parser.js): provides a `req.body` object for routes derived from [body-parser](https://github.com/expressjs/body-parser).
|
||||
- [express-mapper](https://github.com/kethinov/page.js-express-mapper.js): provides a direct imitation of the [Express](http://expressjs.com/) API so you can share controller code on the client and the server with your Express application without modification.
|
||||
|
||||
Please submit pull requests to add more to this list.
|
||||
|
||||
### Running tests
|
||||
|
||||
In the console:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm test
|
||||
```
|
||||
|
||||
In the browser:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm run serve
|
||||
$ open http://localhost:3000/
|
||||
```
|
||||
|
||||
### Support in IE8+
|
||||
|
||||
If you want the router to work in older version of Internet Explorer that don't support pushState, you can use the [HTML5-History-API](https://github.com/devote/HTML5-History-API) polyfill:
|
||||
```bash
|
||||
npm install html5-history-api
|
||||
```
|
||||
|
||||
##### How to use a Polyfill together with router (OPTIONAL):
|
||||
If your web app is located within a nested basepath, you will need to specify the `basepath` for the HTML5-History-API polyfill.
|
||||
Before calling `page.base()` use: `history.redirect([prefixType], [basepath])` - Translation link if required.
|
||||
* `prefixType`: `[string|null]` - Substitute the string after the anchor (#) by default "/".
|
||||
* `basepath`: `[string|null]` - Set the base path. See `page.base()` by default "/". (Note: Slash after `pathname` required)
|
||||
|
||||
### Pull Requests
|
||||
|
||||
* Break commits into a single objective.
|
||||
* An objective should be a chunk of code that is related but requires explanation.
|
||||
* Commits should be in the form of what-it-is: how-it-does-it and or why-it's-needed or what-it-is for trivial changes
|
||||
* Pull requests and commits should be a guide to the code.
|
||||
|
||||
## Server configuration
|
||||
|
||||
In order to load and update any URL managed by page.js, you need to configure your environment to point to your project's main file (index.html, for example) for each non-existent URL. Below you will find examples for most common server scenarios.
|
||||
|
||||
### Nginx
|
||||
|
||||
If using Nginx, add this to the .conf file related to your project (inside the "server" part), and **reload** your Nginx server:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html?$args;
|
||||
}
|
||||
```
|
||||
|
||||
### Apache
|
||||
|
||||
If using Apache, create (or add to) the `.htaccess` file in the root of your public folder, with the code:
|
||||
|
||||
```apache
|
||||
Options +FollowSymLinks
|
||||
RewriteEngine On
|
||||
|
||||
RewriteCond %{SCRIPT_FILENAME} !-d
|
||||
RewriteCond %{SCRIPT_FILENAME} !-f
|
||||
|
||||
RewriteRule ^.*$ ./index.html
|
||||
```
|
||||
|
||||
### Node.js - Express
|
||||
|
||||
For development and/or production, using **Express**, you need to use `express-history-api-fallback` package. An example:
|
||||
|
||||
```js
|
||||
import { join } from 'path';
|
||||
import express from 'express';
|
||||
import history from 'express-history-api-fallback';
|
||||
|
||||
const app = express();
|
||||
const root = join(__dirname, '../public');
|
||||
|
||||
app.use(express.static(root));
|
||||
app.use(history('index.html', { root }));
|
||||
|
||||
const server = app.listen(process.env.PORT || 3000);
|
||||
|
||||
export default server;
|
||||
```
|
||||
|
||||
### Node.js - Browsersync
|
||||
|
||||
For development using **Browsersync**, you need to use `history-api-fallback` package. An example:
|
||||
|
||||
```js
|
||||
var browserSync = require("browser-sync").create();
|
||||
var historyApiFallback = require('connect-history-api-fallback');
|
||||
|
||||
browserSync.init({
|
||||
files: ["*.html", "css/*.css", "js/*.js"],
|
||||
server: {
|
||||
baseDir: ".",
|
||||
middleware: [ historyApiFallback() ]
|
||||
},
|
||||
port: 3030
|
||||
});
|
||||
```
|
||||
|
||||
## Integrations
|
||||
|
||||
### React
|
||||
|
||||
For development using **React**, you can use [pagify-it](https://github.com/sonaye/pagify-it).
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
805
node_modules/page/index.js
generated
vendored
Executable file
805
node_modules/page/index.js
generated
vendored
Executable file
@@ -0,0 +1,805 @@
|
||||
/* globals require, module */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var pathtoRegexp = require('path-to-regexp');
|
||||
|
||||
/**
|
||||
* Short-cuts for global-object checks
|
||||
*/
|
||||
|
||||
var hasDocument = ('undefined' !== typeof document);
|
||||
var hasWindow = ('undefined' !== typeof window);
|
||||
var hasHistory = ('undefined' !== typeof history);
|
||||
var hasProcess = typeof process !== 'undefined';
|
||||
|
||||
/**
|
||||
* Detect click event
|
||||
*/
|
||||
var clickEvent = hasDocument && document.ontouchstart ? 'touchstart' : 'click';
|
||||
|
||||
/**
|
||||
* To work properly with the URL
|
||||
* history.location generated polyfill in https://github.com/devote/HTML5-History-API
|
||||
*/
|
||||
|
||||
var isLocation = hasWindow && !!(window.history.location || window.location);
|
||||
|
||||
/**
|
||||
* The page instance
|
||||
* @api private
|
||||
*/
|
||||
function Page() {
|
||||
// public things
|
||||
this.callbacks = [];
|
||||
this.exits = [];
|
||||
this.current = '';
|
||||
this.len = 0;
|
||||
|
||||
// private things
|
||||
this._decodeURLComponents = true;
|
||||
this._base = '';
|
||||
this._strict = false;
|
||||
this._running = false;
|
||||
this._hashbang = false;
|
||||
|
||||
// bound functions
|
||||
this.clickHandler = this.clickHandler.bind(this);
|
||||
this._onpopstate = this._onpopstate.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the instance of page. This can be called multiple times.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.configure = function(options) {
|
||||
var opts = options || {};
|
||||
|
||||
this._window = opts.window || (hasWindow && window);
|
||||
this._decodeURLComponents = opts.decodeURLComponents !== false;
|
||||
this._popstate = opts.popstate !== false && hasWindow;
|
||||
this._click = opts.click !== false && hasDocument;
|
||||
this._hashbang = !!opts.hashbang;
|
||||
|
||||
var _window = this._window;
|
||||
if(this._popstate) {
|
||||
_window.addEventListener('popstate', this._onpopstate, false);
|
||||
} else if(hasWindow) {
|
||||
_window.removeEventListener('popstate', this._onpopstate, false);
|
||||
}
|
||||
|
||||
if (this._click) {
|
||||
_window.document.addEventListener(clickEvent, this.clickHandler, false);
|
||||
} else if(hasDocument) {
|
||||
_window.document.removeEventListener(clickEvent, this.clickHandler, false);
|
||||
}
|
||||
|
||||
if(this._hashbang && hasWindow && !hasHistory) {
|
||||
_window.addEventListener('hashchange', this._onpopstate, false);
|
||||
} else if(hasWindow) {
|
||||
_window.removeEventListener('hashchange', this._onpopstate, false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set basepath to `path`.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.base = function(path) {
|
||||
if (0 === arguments.length) return this._base;
|
||||
this._base = path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the `base`, which depends on whether we are using History or
|
||||
* hashbang routing.
|
||||
|
||||
* @api private
|
||||
*/
|
||||
Page.prototype._getBase = function() {
|
||||
var base = this._base;
|
||||
if(!!base) return base;
|
||||
var loc = hasWindow && this._window && this._window.location;
|
||||
|
||||
if(hasWindow && this._hashbang && loc && loc.protocol === 'file:') {
|
||||
base = loc.pathname;
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set strict path matching to `enable`
|
||||
*
|
||||
* @param {boolean} enable
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.strict = function(enable) {
|
||||
if (0 === arguments.length) return this._strict;
|
||||
this._strict = enable;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind with the given `options`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `click` bind to click events [true]
|
||||
* - `popstate` bind to popstate [true]
|
||||
* - `dispatch` perform initial dispatch [true]
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.start = function(options) {
|
||||
var opts = options || {};
|
||||
this.configure(opts);
|
||||
|
||||
if (false === opts.dispatch) return;
|
||||
this._running = true;
|
||||
|
||||
var url;
|
||||
if(isLocation) {
|
||||
var window = this._window;
|
||||
var loc = window.location;
|
||||
|
||||
if(this._hashbang && ~loc.hash.indexOf('#!')) {
|
||||
url = loc.hash.substr(2) + loc.search;
|
||||
} else if (this._hashbang) {
|
||||
url = loc.search + loc.hash;
|
||||
} else {
|
||||
url = loc.pathname + loc.search + loc.hash;
|
||||
}
|
||||
}
|
||||
|
||||
this.replace(url, null, true, opts.dispatch);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind click and popstate event handlers.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.stop = function() {
|
||||
if (!this._running) return;
|
||||
this.current = '';
|
||||
this.len = 0;
|
||||
this._running = false;
|
||||
|
||||
var window = this._window;
|
||||
this._click && window.document.removeEventListener(clickEvent, this.clickHandler, false);
|
||||
hasWindow && window.removeEventListener('popstate', this._onpopstate, false);
|
||||
hasWindow && window.removeEventListener('hashchange', this._onpopstate, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show `path` with optional `state` object.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {Object=} state
|
||||
* @param {boolean=} dispatch
|
||||
* @param {boolean=} push
|
||||
* @return {!Context}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.show = function(path, state, dispatch, push) {
|
||||
var ctx = new Context(path, state, this),
|
||||
prev = this.prevContext;
|
||||
this.prevContext = ctx;
|
||||
this.current = ctx.path;
|
||||
if (false !== dispatch) this.dispatch(ctx, prev);
|
||||
if (false !== ctx.handled && false !== push) ctx.pushState();
|
||||
return ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
* Goes back in the history
|
||||
* Back should always let the current route push state and then go back.
|
||||
*
|
||||
* @param {string} path - fallback path to go back if no more history exists, if undefined defaults to page.base
|
||||
* @param {Object=} state
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.back = function(path, state) {
|
||||
var page = this;
|
||||
if (this.len > 0) {
|
||||
var window = this._window;
|
||||
// this may need more testing to see if all browsers
|
||||
// wait for the next tick to go back in history
|
||||
hasHistory && window.history.back();
|
||||
this.len--;
|
||||
} else if (path) {
|
||||
setTimeout(function() {
|
||||
page.show(path, state);
|
||||
});
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
page.show(page._getBase(), state);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register route to redirect from one path to other
|
||||
* or just redirect to another route
|
||||
*
|
||||
* @param {string} from - if param 'to' is undefined redirects to 'from'
|
||||
* @param {string=} to
|
||||
* @api public
|
||||
*/
|
||||
Page.prototype.redirect = function(from, to) {
|
||||
var inst = this;
|
||||
|
||||
// Define route from a path to another
|
||||
if ('string' === typeof from && 'string' === typeof to) {
|
||||
page.call(this, from, function(e) {
|
||||
setTimeout(function() {
|
||||
inst.replace(/** @type {!string} */ (to));
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the push state and replace it with another
|
||||
if ('string' === typeof from && 'undefined' === typeof to) {
|
||||
setTimeout(function() {
|
||||
inst.replace(from);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace `path` with optional `state` object.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {Object=} state
|
||||
* @param {boolean=} init
|
||||
* @param {boolean=} dispatch
|
||||
* @return {!Context}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
Page.prototype.replace = function(path, state, init, dispatch) {
|
||||
var ctx = new Context(path, state, this),
|
||||
prev = this.prevContext;
|
||||
this.prevContext = ctx;
|
||||
this.current = ctx.path;
|
||||
ctx.init = init;
|
||||
ctx.save(); // save before dispatching, which may redirect
|
||||
if (false !== dispatch) this.dispatch(ctx, prev);
|
||||
return ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch the given `ctx`.
|
||||
*
|
||||
* @param {Context} ctx
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Page.prototype.dispatch = function(ctx, prev) {
|
||||
var i = 0, j = 0, page = this;
|
||||
|
||||
function nextExit() {
|
||||
var fn = page.exits[j++];
|
||||
if (!fn) return nextEnter();
|
||||
fn(prev, nextExit);
|
||||
}
|
||||
|
||||
function nextEnter() {
|
||||
var fn = page.callbacks[i++];
|
||||
|
||||
if (ctx.path !== page.current) {
|
||||
ctx.handled = false;
|
||||
return;
|
||||
}
|
||||
if (!fn) return unhandled.call(page, ctx);
|
||||
fn(ctx, nextEnter);
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
nextExit();
|
||||
} else {
|
||||
nextEnter();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register an exit route on `path` with
|
||||
* callback `fn()`, which will be called
|
||||
* on the previous context when a new
|
||||
* page is visited.
|
||||
*/
|
||||
Page.prototype.exit = function(path, fn) {
|
||||
if (typeof path === 'function') {
|
||||
return this.exit('*', path);
|
||||
}
|
||||
|
||||
var route = new Route(path, null, this);
|
||||
for (var i = 1; i < arguments.length; ++i) {
|
||||
this.exits.push(route.middleware(arguments[i]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle "click" events.
|
||||
*/
|
||||
|
||||
/* jshint +W054 */
|
||||
Page.prototype.clickHandler = function(e) {
|
||||
if (1 !== this._which(e)) return;
|
||||
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey) return;
|
||||
if (e.defaultPrevented) return;
|
||||
|
||||
// ensure link
|
||||
// use shadow dom when available if not, fall back to composedPath()
|
||||
// for browsers that only have shady
|
||||
var el = e.target;
|
||||
var eventPath = e.path || (e.composedPath ? e.composedPath() : null);
|
||||
|
||||
if(eventPath) {
|
||||
for (var i = 0; i < eventPath.length; i++) {
|
||||
if (!eventPath[i].nodeName) continue;
|
||||
if (eventPath[i].nodeName.toUpperCase() !== 'A') continue;
|
||||
if (!eventPath[i].href) continue;
|
||||
|
||||
el = eventPath[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// continue ensure link
|
||||
// el.nodeName for svg links are 'a' instead of 'A'
|
||||
while (el && 'A' !== el.nodeName.toUpperCase()) el = el.parentNode;
|
||||
if (!el || 'A' !== el.nodeName.toUpperCase()) return;
|
||||
|
||||
// check if link is inside an svg
|
||||
// in this case, both href and target are always inside an object
|
||||
var svg = (typeof el.href === 'object') && el.href.constructor.name === 'SVGAnimatedString';
|
||||
|
||||
// Ignore if tag has
|
||||
// 1. "download" attribute
|
||||
// 2. rel="external" attribute
|
||||
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') return;
|
||||
|
||||
// ensure non-hash for the same path
|
||||
var link = el.getAttribute('href');
|
||||
if(!this._hashbang && this._samePath(el) && (el.hash || '#' === link)) return;
|
||||
|
||||
// Check for mailto: in the href
|
||||
if (link && link.indexOf('mailto:') > -1) return;
|
||||
|
||||
// check target
|
||||
// svg target is an object and its desired value is in .baseVal property
|
||||
if (svg ? el.target.baseVal : el.target) return;
|
||||
|
||||
// x-origin
|
||||
// note: svg links that are not relative don't call click events (and skip page.js)
|
||||
// consequently, all svg links tested inside page.js are relative and in the same origin
|
||||
if (!svg && !this.sameOrigin(el.href)) return;
|
||||
|
||||
// rebuild path
|
||||
// There aren't .pathname and .search properties in svg links, so we use href
|
||||
// Also, svg href is an object and its desired value is in .baseVal property
|
||||
var path = svg ? el.href.baseVal : (el.pathname + el.search + (el.hash || ''));
|
||||
|
||||
path = path[0] !== '/' ? '/' + path : path;
|
||||
|
||||
// strip leading "/[drive letter]:" on NW.js on Windows
|
||||
if (hasProcess && path.match(/^\/[a-zA-Z]:\//)) {
|
||||
path = path.replace(/^\/[a-zA-Z]:\//, '/');
|
||||
}
|
||||
|
||||
// same page
|
||||
var orig = path;
|
||||
var pageBase = this._getBase();
|
||||
|
||||
if (path.indexOf(pageBase) === 0) {
|
||||
path = path.substr(pageBase.length);
|
||||
}
|
||||
|
||||
if (this._hashbang) path = path.replace('#!', '');
|
||||
|
||||
if (pageBase && orig === path && (!isLocation || this._window.location.protocol !== 'file:')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.show(orig);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle "populate" events.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Page.prototype._onpopstate = (function () {
|
||||
var loaded = false;
|
||||
if ( ! hasWindow ) {
|
||||
return function () {};
|
||||
}
|
||||
if (hasDocument && document.readyState === 'complete') {
|
||||
loaded = true;
|
||||
} else {
|
||||
window.addEventListener('load', function() {
|
||||
setTimeout(function() {
|
||||
loaded = true;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
return function onpopstate(e) {
|
||||
if (!loaded) return;
|
||||
var page = this;
|
||||
if (e.state) {
|
||||
var path = e.state.path;
|
||||
page.replace(path, e.state);
|
||||
} else if (isLocation) {
|
||||
var loc = page._window.location;
|
||||
page.show(loc.pathname + loc.search + loc.hash, undefined, undefined, false);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Event button.
|
||||
*/
|
||||
Page.prototype._which = function(e) {
|
||||
e = e || (hasWindow && this._window.event);
|
||||
return null == e.which ? e.button : e.which;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert to a URL object
|
||||
* @api private
|
||||
*/
|
||||
Page.prototype._toURL = function(href) {
|
||||
var window = this._window;
|
||||
if(typeof URL === 'function' && isLocation) {
|
||||
return new URL(href, window.location.toString());
|
||||
} else if (hasDocument) {
|
||||
var anc = window.document.createElement('a');
|
||||
anc.href = href;
|
||||
return anc;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if `href` is the same origin.
|
||||
* @param {string} href
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Page.prototype.sameOrigin = function(href) {
|
||||
if(!href || !isLocation) return false;
|
||||
|
||||
var url = this._toURL(href);
|
||||
var window = this._window;
|
||||
|
||||
var loc = window.location;
|
||||
return loc.protocol === url.protocol &&
|
||||
loc.hostname === url.hostname &&
|
||||
loc.port === url.port;
|
||||
};
|
||||
|
||||
/**
|
||||
* @api private
|
||||
*/
|
||||
Page.prototype._samePath = function(url) {
|
||||
if(!isLocation) return false;
|
||||
var window = this._window;
|
||||
var loc = window.location;
|
||||
return url.pathname === loc.pathname &&
|
||||
url.search === loc.search;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove URL encoding from the given `str`.
|
||||
* Accommodates whitespace in both x-www-form-urlencoded
|
||||
* and regular percent-encoded form.
|
||||
*
|
||||
* @param {string} val - URL component to decode
|
||||
* @api private
|
||||
*/
|
||||
Page.prototype._decodeURLEncodedURIComponent = function(val) {
|
||||
if (typeof val !== 'string') { return val; }
|
||||
return this._decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, ' ')) : val;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new `page` instance and function
|
||||
*/
|
||||
function createPage() {
|
||||
var pageInstance = new Page();
|
||||
|
||||
function pageFn(/* args */) {
|
||||
return page.apply(pageInstance, arguments);
|
||||
}
|
||||
|
||||
// Copy all of the things over. In 2.0 maybe we use setPrototypeOf
|
||||
pageFn.callbacks = pageInstance.callbacks;
|
||||
pageFn.exits = pageInstance.exits;
|
||||
pageFn.base = pageInstance.base.bind(pageInstance);
|
||||
pageFn.strict = pageInstance.strict.bind(pageInstance);
|
||||
pageFn.start = pageInstance.start.bind(pageInstance);
|
||||
pageFn.stop = pageInstance.stop.bind(pageInstance);
|
||||
pageFn.show = pageInstance.show.bind(pageInstance);
|
||||
pageFn.back = pageInstance.back.bind(pageInstance);
|
||||
pageFn.redirect = pageInstance.redirect.bind(pageInstance);
|
||||
pageFn.replace = pageInstance.replace.bind(pageInstance);
|
||||
pageFn.dispatch = pageInstance.dispatch.bind(pageInstance);
|
||||
pageFn.exit = pageInstance.exit.bind(pageInstance);
|
||||
pageFn.configure = pageInstance.configure.bind(pageInstance);
|
||||
pageFn.sameOrigin = pageInstance.sameOrigin.bind(pageInstance);
|
||||
pageFn.clickHandler = pageInstance.clickHandler.bind(pageInstance);
|
||||
|
||||
pageFn.create = createPage;
|
||||
|
||||
Object.defineProperty(pageFn, 'len', {
|
||||
get: function(){
|
||||
return pageInstance.len;
|
||||
},
|
||||
set: function(val) {
|
||||
pageInstance.len = val;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(pageFn, 'current', {
|
||||
get: function(){
|
||||
return pageInstance.current;
|
||||
},
|
||||
set: function(val) {
|
||||
pageInstance.current = val;
|
||||
}
|
||||
});
|
||||
|
||||
// In 2.0 these can be named exports
|
||||
pageFn.Context = Context;
|
||||
pageFn.Route = Route;
|
||||
|
||||
return pageFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register `path` with callback `fn()`,
|
||||
* or route `path`, or redirection,
|
||||
* or `page.start()`.
|
||||
*
|
||||
* page(fn);
|
||||
* page('*', fn);
|
||||
* page('/user/:id', load, user);
|
||||
* page('/user/' + user.id, { some: 'thing' });
|
||||
* page('/user/' + user.id);
|
||||
* page('/from', '/to')
|
||||
* page();
|
||||
*
|
||||
* @param {string|!Function|!Object} path
|
||||
* @param {Function=} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function page(path, fn) {
|
||||
// <callback>
|
||||
if ('function' === typeof path) {
|
||||
return page.call(this, '*', path);
|
||||
}
|
||||
|
||||
// route <path> to <callback ...>
|
||||
if ('function' === typeof fn) {
|
||||
var route = new Route(/** @type {string} */ (path), null, this);
|
||||
for (var i = 1; i < arguments.length; ++i) {
|
||||
this.callbacks.push(route.middleware(arguments[i]));
|
||||
}
|
||||
// show <path> with [state]
|
||||
} else if ('string' === typeof path) {
|
||||
this['string' === typeof fn ? 'redirect' : 'show'](path, fn);
|
||||
// start [options]
|
||||
} else {
|
||||
this.start(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhandled `ctx`. When it's not the initial
|
||||
* popstate then redirect. If you wish to handle
|
||||
* 404s on your own use `page('*', callback)`.
|
||||
*
|
||||
* @param {Context} ctx
|
||||
* @api private
|
||||
*/
|
||||
function unhandled(ctx) {
|
||||
if (ctx.handled) return;
|
||||
var current;
|
||||
var page = this;
|
||||
var window = page._window;
|
||||
|
||||
if (page._hashbang) {
|
||||
current = isLocation && this._getBase() + window.location.hash.replace('#!', '');
|
||||
} else {
|
||||
current = isLocation && window.location.pathname + window.location.search;
|
||||
}
|
||||
|
||||
if (current === ctx.canonicalPath) return;
|
||||
page.stop();
|
||||
ctx.handled = false;
|
||||
isLocation && (window.location.href = ctx.canonicalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes RegExp characters in the given string.
|
||||
*
|
||||
* @param {string} s
|
||||
* @api private
|
||||
*/
|
||||
function escapeRegExp(s) {
|
||||
return s.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new "request" `Context`
|
||||
* with the given `path` and optional initial `state`.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} path
|
||||
* @param {Object=} state
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Context(path, state, pageInstance) {
|
||||
var _page = this.page = pageInstance || page;
|
||||
var window = _page._window;
|
||||
var hashbang = _page._hashbang;
|
||||
|
||||
var pageBase = _page._getBase();
|
||||
if ('/' === path[0] && 0 !== path.indexOf(pageBase)) path = pageBase + (hashbang ? '#!' : '') + path;
|
||||
var i = path.indexOf('?');
|
||||
|
||||
this.canonicalPath = path;
|
||||
var re = new RegExp('^' + escapeRegExp(pageBase));
|
||||
this.path = path.replace(re, '') || '/';
|
||||
if (hashbang) this.path = this.path.replace('#!', '') || '/';
|
||||
|
||||
this.title = (hasDocument && window.document.title);
|
||||
this.state = state || {};
|
||||
this.state.path = path;
|
||||
this.querystring = ~i ? _page._decodeURLEncodedURIComponent(path.slice(i + 1)) : '';
|
||||
this.pathname = _page._decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path);
|
||||
this.params = {};
|
||||
|
||||
// fragment
|
||||
this.hash = '';
|
||||
if (!hashbang) {
|
||||
if (!~this.path.indexOf('#')) return;
|
||||
var parts = this.path.split('#');
|
||||
this.path = this.pathname = parts[0];
|
||||
this.hash = _page._decodeURLEncodedURIComponent(parts[1]) || '';
|
||||
this.querystring = this.querystring.split('#')[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push state.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Context.prototype.pushState = function() {
|
||||
var page = this.page;
|
||||
var window = page._window;
|
||||
var hashbang = page._hashbang;
|
||||
|
||||
page.len++;
|
||||
if (hasHistory) {
|
||||
window.history.pushState(this.state, this.title,
|
||||
hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the context state.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Context.prototype.save = function() {
|
||||
var page = this.page;
|
||||
if (hasHistory) {
|
||||
page._window.history.replaceState(this.state, this.title,
|
||||
page._hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize `Route` with the given HTTP `path`,
|
||||
* and an array of `callbacks` and `options`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
* - `strict` enable strict matching for trailing slashes
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} path
|
||||
* @param {Object=} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Route(path, options, page) {
|
||||
var _page = this.page = page || globalPage;
|
||||
var opts = options || {};
|
||||
opts.strict = opts.strict || page._strict;
|
||||
this.path = (path === '*') ? '(.*)' : path;
|
||||
this.method = 'GET';
|
||||
this.regexp = pathtoRegexp(this.path, this.keys = [], opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return route middleware with
|
||||
* the given callback `fn()`.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Route.prototype.middleware = function(fn) {
|
||||
var self = this;
|
||||
return function(ctx, next) {
|
||||
if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this route matches `path`, if so
|
||||
* populate `params`.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {Object} params
|
||||
* @return {boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype.match = function(path, params) {
|
||||
var keys = this.keys,
|
||||
qsIndex = path.indexOf('?'),
|
||||
pathname = ~qsIndex ? path.slice(0, qsIndex) : path,
|
||||
m = this.regexp.exec(decodeURIComponent(pathname));
|
||||
|
||||
if (!m) return false;
|
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
var key = keys[i - 1];
|
||||
var val = this.page._decodeURLEncodedURIComponent(m[i]);
|
||||
if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
|
||||
params[key.name] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
var globalPage = createPage();
|
||||
module.exports = globalPage;
|
||||
module.exports.default = globalPage;
|
88
node_modules/page/package.json
generated
vendored
Normal file
88
node_modules/page/package.json
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"_from": "page@^1.11.4",
|
||||
"_id": "page@1.11.4",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-8JMZzcE5W4qk+/DtmogN57cI+Yscy7xTYCpfSO7s3Tx6LjZuAfHFQY1+cKIAy60NaXdzVD6nOc3objaVbE0HJg==",
|
||||
"_location": "/page",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "page@^1.11.4",
|
||||
"name": "page",
|
||||
"escapedName": "page",
|
||||
"rawSpec": "^1.11.4",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^1.11.4"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/page/-/page-1.11.4.tgz",
|
||||
"_shasum": "db4d9c4d4bd92c49ad3dc5bd3c1a1201df992bd8",
|
||||
"_spec": "page@^1.11.4",
|
||||
"_where": "/home/s2/tmp/vanillajs-seed",
|
||||
"browser": "page.js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/visionmedia/page.js/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"component": {
|
||||
"scripts": {
|
||||
"page": "index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"path-to-regexp": "~1.2.1"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Tiny client-side router",
|
||||
"devDependencies": {
|
||||
"chai": "^1.10.0",
|
||||
"coveralls": "^2.11.2",
|
||||
"engine-dependencies": "^0.2.12",
|
||||
"express": "^4.10.2",
|
||||
"jade": "^1.7.0",
|
||||
"jscoverage": "^0.5.9",
|
||||
"jsdom": "^11.5.1",
|
||||
"jshint": "^2.5.10",
|
||||
"mocha": "^2.0.1",
|
||||
"mocha-lcov-reporter": "0.0.1",
|
||||
"rollup": "^0.54.1",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-resolve": "^3.0.2",
|
||||
"serve": "*",
|
||||
"should": "*"
|
||||
},
|
||||
"engineDependencies": {
|
||||
"node": {
|
||||
"0.10.x": {
|
||||
"devDependencies": {
|
||||
"jsdom": "^1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"page.js",
|
||||
"page.mjs"
|
||||
],
|
||||
"homepage": "https://github.com/visionmedia/page.js#readme",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"module": "page.mjs",
|
||||
"name": "page",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/visionmedia/page.js.git"
|
||||
},
|
||||
"scripts": {
|
||||
"engine-deps": "install-engine-dependencies",
|
||||
"make": "rollup -c rollup.config.js",
|
||||
"serve": "serve test",
|
||||
"test": "jshint index.js test/tests.js && mocha test/tests.js",
|
||||
"test-cov": "jscoverage index.js index-cov.js; PAGE_COV=1 mocha test/tests.js -R html-cov > coverage.html"
|
||||
},
|
||||
"version": "1.11.4"
|
||||
}
|
1211
node_modules/page/page.js
generated
vendored
Normal file
1211
node_modules/page/page.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1203
node_modules/page/page.mjs
generated
vendored
Normal file
1203
node_modules/page/page.mjs
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user