Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't expose jQuery to window with AMD #557

Closed
wants to merge 1 commit into from

Conversation

ryanflorence
Copy link

Ticket: http://bugs.jquery.com/ticket/10545

One of the great features of AMD is not touching the global namespace outside of require and define.

I don't want my team and I to use jQuery w/o deliberating requiring it into a module, additionally the RequireJS optimizer will know to include it.

For people requiring from a CDN, they just configure the paths, no need for a global jQuery object in an AMD environment:

var require = {
  paths: {
    "jquery": "http://code.jquery.com/jquery-1.7b2.js"
  }
};

@rwaldron
Copy link
Member

@rpflorence can you file a ticket for this? http://bugs.jquery.com

@ryanflorence
Copy link
Author

Ticket filed, added to first comment - http://bugs.jquery.com/ticket/10545

@ryanflorence
Copy link
Author

Ping: @jrburke

@timmywil
Copy link
Member

I agree this is the way it should be done.

@@ -935,20 +935,6 @@ function doScrollCheck() {
jQuery.ready();
}

// Expose jQuery as an AMD module, but only for AMD loaders that
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed from core.js because outro is where the jQuery object is "exported".

@addyosmani
Copy link
Member

Agreed - ticket marked as valid. http://bugs.jquery.com/ticket/10545

@rwaldron
Copy link
Member

@rpflorence thanks for filing the ticket :)

@jrburke
Copy link
Contributor

jrburke commented Oct 20, 2011

I appreciate wanting to do this, but I'm not sure it is safe to do so given how much jQuery is used. One of the reasons the original AMD patch was pulled from 1.6 was concerns about how multiple versions of jQuery would interact with AMD, since some pages can inadvertently load more than one version of jQuery.

The define.amd.jQuery was added so that an AMD loader could broadcast that it knew there could be a few jQuerys loaded on a page and had mitigations so that the developer who was using an AMD loader could receive the correct one. In requirejs, there is multversion support and also a way in the config to specify only accepting a specific version of jquery in the define call.

My concern with this patch is that a third party lib that uses jQuery is loaded on a page with an AMD loader. This patch would mean the AMD loader would get that version of jQuery but the third party lib would not see a global jQuery object, and errors would occur. What is worse, the AMD loader may just outright reject that jQuery registration call if it is looking for a different jQuery version.

If jQuery were not as popular as it is now, or started off always with this logic, it would not be that much of a concern. In fact if it was any other JS library out there, the approach in this patch is the right one. However, for jQuery, I believe it will lead to errors and elevated support requests.

I do not have a complete understanding of the magnitude of this hazard, the jQuery core folks can answer that better. But this concern is what lead me doing the AMD registration as it is today.

For AMD folks that do not want to export a global, they can do an optimized build and call jQuery.noConflict() to get this effect (with the build there should not be a window when the AMD-desired jQuery interferes with another one in the page). Not the ideal, but sometimes these kinds of things are needed to upgrade the web.

It would be good to get feedback on what people think is the relative threat of problems as outlined above, but I think given jQuery's special status doing the dual registration is the less disruptive route.

@ryanflorence
Copy link
Author

Thanks James, that's why I pulled you in, I was sure there might be some issues.

My concern with this patch is that a third party lib that uses jQuery is loaded on a page with an AMD loader. This patch would mean the AMD loader would get that version of jQuery but the third party lib would not see a global jQuery object, and errors would occur.

A quick jQuery.noConflict() in the main.js is the safest bet, for sure. However, I believe that if somebody is building their app with AMD, they will be aware of this. I feel like if you're going to support AMD, support AMD all the way, incentivizing third party libs to support AMD as well.

I'm also curious what the repercussions would be and (hope) they're worth it.

@jrburke
Copy link
Contributor

jrburke commented Oct 20, 2011

A quick jQuery.noConflict() in the main.js is the safest bet, for sure. However, I believe that if somebody is building their app with AMD, they will be aware of this. I feel like if you're going to support AMD, support AMD all the way, incentivizing third party libs to support AMD as well.

The problem is the code change hurts people that are not opting in to AMD, which at the moment is a larger set of people, and the error may be tricky for them to track down, indeed it can break code that no longer has an owner. I think, in jQuery's case, having the people embracing the future do a little bit more work is the less disruptive path, and maybe by a jQuery 2.0 the behavior could be changed to this logic of either/or.

But it would be good to get a read from the jQuery core members on their assessment of the risk. I would be happy to go this either/or path if the core folks felt the risk is manageable.

Maybe I am overestimating how much jQuery is used in third party code that ends up on pages, and maybe we just need to document how they can temporarily override define() while their bundled code executes to avoid the issue, then set back define() when done.

@timmywil
Copy link
Member

I suppose jQuery.noConflict(true) is an easy solution. I would like to see it completely modular, but @jrburke's prediction of many support requests would certainly be true.

Given the sheer number of jQuery plugins, it would be difficult to require editing each one to conform to AMD. And yet, not impossible. If you're using an AMD loader, you already know how it works and I'm sure you already have several concerns if you're loading multiple versions of jQuery on the page.

Anyway, I'm flipflopping.

@dmethvin
Copy link
Member

dmethvin commented Nov 6, 2011

Per http://bugs.jquery.com/ticket/10545 we are destroying that flipflopper timmywil's political career.

@dmethvin dmethvin closed this Nov 6, 2011
@ryanflorence
Copy link
Author

I agree, this is probably the best decision after further thought. I still think, however, that the AMD stuff should move into the outro so all "exporting" of jQuery is done in the same place.

On Nov 6, 2011, at 2:45 PM, Dave Methvin wrote:

Per http://bugs.jquery.com/ticket/10545 we are destroying that flipflopper timmywil's political career.


Reply to this email directly or view it on GitHub:
#557 (comment)

@jrburke
Copy link
Contributor

jrburke commented Nov 7, 2011

I still think, however, that the AMD stuff should move into the outro so all "exporting" of jQuery is done in the same place.

@rpflorence, I think it still may move to the outro to help with the issue found as described in this ticket, even if the global is kept.

@SlexAxton
Copy link
Member

I didn't know about this ticket, but as someone who uses AMD jQuery in production on lots of third party sites, I'm glad that I'm the one in control of the noConflicting and globals. People add all kinds of crazy stuff to their pages, and I think the group that we should target to do the extra work is the one that likely knows more about what they're doing. So +1 to moving to the outro, but keeping the global (even though I destroy it everytime).

@jrburke
Copy link
Contributor

jrburke commented Nov 7, 2011

Now that I think about it a bit more, I'm not sure the noConflict call will work in the AMD case, at least for loaders that immediately call dependencies once define is called (so curl, not requirejs) until the define call happens after the window assignment in outro. I'll work up a patch.

@jrburke
Copy link
Contributor

jrburke commented Nov 7, 2011

@nfriedly
Copy link

I'm having an issue that's related to this. My company makes a widget that includes jQuery and gets put on our customer's pages. We recently switched to using require.js instead of just concatenating all of our script files, and got an angry phonecall from one of our customers shortly after deploying this change that we were breaking their pages:

Basically, what happened is that during the 4 ms between when our jQuery's define() is called and when my main.js's require() is called, our copy of jQuery is the global one and it's "stealing" the customer's jQuery plugins. When our main code calls jQuery.noConflict(true), it's too late and our copy already has their plugin.

Here's a trimmed-down example of the issue: http://nfriedly.com/stuff/jquery-requirejs-noconflict-issue/example.html? (Note that the issue is depends on timing. If it doesn't occur initially, try clicking the 'Reset' link at the bottom.)

I fixed it for my use-case by editing jQuery as seen in http://nfriedly.com/stuff/jquery-requirejs-noconflict-issue/example.html?patched

I'd like to see this changed even if it has to wait for a fairly major release.

@dmethvin
Copy link
Member

Per the discussion above, the problem is that if we don't expose window.jQuery then most of the plugins on the face of the planet won't work unless you are willing to modify them to not expect a global jQuery. If you're willing to do that, just modify jQuery as well.

@dslatten
Copy link

dslatten commented Jun 8, 2013

One of the main selling points of AMD is that it respects the global namespace. Therefore, a common use case is: developers who need a way to reliably load their app/widget (and its dependencies) in an uncontrolled, unpredictable (browser) environment--without impacting that environment's namespace. Yet jQuery's AMD implementation makes this common use case extremely difficult, if not impossible, to achieve.

As @nfriedly pointed out, forcing jQuery into the global namespace of a page that already has jQuery + plugins on it--even if those globals are immediately removed (or rolled back) by jQuery.noConflict(true)--still creates a situation where the native jQuery plugins can erroneously attach to the AMD-loaded jQuery global and break the page. Another major selling point of AMD is that it allows for asynchronous loading. Why then does asynchronously loading jQuery and/or jQuery plugins lead to global namespace collisions?

It sounds like respect for the global namespace and asynchronous loading are, at best, mutually exclusive when working with AMD + jQuery (in the browser). To top it all off, jQuery defines itself as the named module, jquery, thus imposing unnecessary restrictions on jQuery's modularity. I'm starting to wonder if AMD + jQuery has any benefits at all. It's just like regular jQuery...but you have to wait for RequireJS to load first...?

Anyway, I've been working with AMD and jQuery for like...at least 3 weeks now, so I feel perhaps I should leverage that expertise and propose a new solution.

After glancing at jquery/src/exports.js, I notice that the conditions for pulverizing loading the globals are pretty generic:

if ( typeof window === "object" && typeof window.document === "object" ) {
    window.jQuery = window.$ = jQuery;
}

And just before that is the part where jQuery defines itself as AMD-ready:

if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function () { return jQuery; } );
}

Here's what I propose. We (and by that I mean @jrburke) define a new piece of the define.amd spec that says something like this:

An example of how `define.amd` may be defined for an implementation that does not modify the global namespace:
define.amd = {
    noconflict: true
};

Then change the pulverizing conditions to something like this:

if ( typeof window === "object" && typeof window.document === "object" && !define.amd.noconflict ) {
    window.jQuery = window.$ = jQuery;
}

The idea here being: allow an AMD loader to use the define.amd global to communicate to jQuery that it wishes to "opt out" of window.jQuery = window.$ = jQuery;

@jrburke
Copy link
Contributor

jrburke commented Jun 8, 2013

@dslatten: the current structure is a result of the limits of scripts loading (particularly in IE where it does not match up script execution directly followed by a script load event) and the numerous ways in which developers add jQuery to a page. A lot of this will get better when ECMAScript 6 modules come out -- in particular, the ability to create Module Loader instances that can set separate objects for the "global" for each loader.

Here is what could be done today though, for the global setting, as a result of work that has happened since this ticket first opened:

AMD module config could be used to indicate the noConflict behavior. So an AMD-based project could register this config:

require.config({
  config: {
    'jquery': {
      noConflict: true
    }
  }
});

Then, the jQuery exports.js file could look something like this (note the makeGlobal use, comments removed to make the code snippet smaller):

var makeGlobal = true;
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
    module.exports = jQuery;
} else {
    if ( typeof define === "function" && define.amd ) {
        define( "jquery", ["module"], function (module) { 
            if ( module.config && module.config().noConflict ) {
                makeGlobal = false;
            }
            return jQuery; 
        } );
    }
}

if ( makeGlobal && typeof window === "object" && typeof window.document === "object" ) {
    window.jQuery = window.$ = jQuery;
}

If that is something you think might be useful, then I suggest opening a new, targeted ticket about this noConflict approach using module config. Something like "Use AMD module config to avoid setting jQuery global" and keep that ticket focused on just that aspect of AMD/jQuery interaction.

@dslatten
Copy link

dslatten commented Jun 8, 2013

I definitely think it would be useful. But unless I'm mistaken, your code example assumes that jQuery's module ID will always be jquery, and that conflicts with another change I'd like to see happen: stop assuming that jQuery's module ID will always be jquery.

@jrburke
Copy link
Contributor

jrburke commented Jun 8, 2013

The code change mentioned to skip making a global is separate from deciding if jQuery should register as an anonymous module, and is why I suggested breaking it out as a separate, targeted bug, because it can be done regardless of the anon module choice. The module config just needs to match whatever module ID the module ends up with.

Removing the named jquery would provide less benefit than you may initially perceive though and likely will result in more error reports:

Anonymous modules work by being able to tie a script execution to a script tag. This is very difficult to do if a developer just adds jQuery to the page as an HTML script tag manually after an AMD loader is in the page. In that case, the loader can neither reliably ignore that define call nor give it a name, given how browsers execute scripts and how they fire any script onload/readystate events. It will cause some sort of error.

Anon registration is still recommended for most modules, but given how many sites embed jQuery by default and in weird CMS-driven ways, the named 'jquery' avoids those issues.

On the possible benefit of an anonymous jQuery:

  • If you use another third party library that needs jQuery as a dependency, that dependency will likely be using 'jquery' as the dependency name. So, jQuery registering as 'jquery' is not an issue once you have one of those libraries in your project: you should be placing jQuery under the 'jquery' modules ID. Projects that use a module ID like 'vendor/jquery-1.9.1' is an AMD anti-pattern.
  • If creating a standalone library that embeds jQuery and that library does not need to do dynamic loading for its internal parts, then using a build with almond and wrapping that file in a closure will avoid any issues with conflicts with another jQuery on the page.
  • There are things like multiversion contexts and AMD namespace renaming available for standalone libraries that need dynamic loading.

There are definitely use cases that could benefit from jQuery not naming itself. However, the likelihood of errors being caused by manually authored HTML script tags for jquery.js are greater to me, and would lead to more support costs to jQuery and AMD loaders as people would report those errors. If one of those use cases is super important to you, you always have the option to modify your local copy of jquery.js and remove the string yourself.

I do see this as a non-ideal setup, but upgrading the web in the wild is difficult and it is about measuring trade-offs, particularly when relying on uneven script loading capabilities across browsers. The real solution as mentioned before will be ES module loaders, which will not suffer from the "pair anonymous module with ID" limitations that script tags have today.

All that said, I am not a jQuery maintainer, and they could decide differently and be fine with the tradeoffs, go with an anonymous call and point people who report errors to AMD loader sites. I do think they will get reports of issues with that change, but if they are fine with processing those reports with a standard boilerplate of "go talk to your AMD loader of choice for suggestions on how to deal with it", that would be a way to deal with that support cost.

But the choice to create a global via module config vs. switching to an anon call should be tracked and discussed as separate issues.

@dslatten
Copy link

This is my understanding so far:

  • If an AMD-compatible script (one that initiates a define call if an AMD loader is detected) is loaded (via a <script> tag) after an AMD loader--and the AMD loader didn't specify that script as a dependency (i.e., assign it a module ID)--then errors will occur.
  • The workaround is to make sure that AMD-compatible scripts (not native AMD modules) use a named define call. This allows the AMD loader to safely ignore define calls that it didn't initiate.
  • The only JavaScript library that is expected to implement this workaround is jQuery. Less-popular libraries should choose between (1) not being AMD-compatible, or (2) not being <script>-compatible on pages that use an AMD loader (unless loaded before it).
  • Only the first define call (e.g., for jquery) is accepted by the AMD loader. Subsequent define calls are safely ignored. As a result, AMD modules that depend on jQuery cannot reliably execute in an uncontrolled environment, because there's no guarantee that jquery will return the correct version of jQuery. In fact, there's no guarantee that jquery will return _any_ version of jQuery; it's entirely possible that another script has already defined a module called jquery and mapped it to some arbitrary custom script.
If you use another third party library that needs jQuery as a dependency, that dependency will likely be using 'jquery' as the dependency name.

This is only likely because it's hard-coded into jQuery itself! Otherwise, the most-likely choice would be the filename that jQuery is distributed by (minus the .js extension), because it requires the least amount of effort (e.g., path configuration or renaming files).

So, jQuery registering as 'jquery' is not an issue once you have one of those libraries in your project...

Based on my understanding, it's still an issue in at least 2 cases:

  1. Different versions of jQuery are needed within the same page (by different AMD modules that are unaware of the other's existence).
  2. Someone customizes a copy of jQuery, but doesn't change the part that defines it as the jquery module.
...you should be placing jQuery under the 'jquery' modules ID. Projects that use a module ID like 'vendor/jquery-1.9.1' is an AMD anti-pattern.

So in an effort to encourage modular/reusable/interoperable code, AMD recommends a best practice that all-but-guarantees namespace collisions and interoperability limitations?

If creating a standalone library that embeds jQuery and that library does not need to do dynamic loading for its internal parts, then using a build with almond and wrapping that file in a closure will avoid any issues with conflicts with another jQuery on the page.

So if I create an app according to the Asynchrous Module Definition specs, and I need my app to asynchronously load JavaScript modules (including jQuery), then my only option is to build a version that isn't modular and doesn't load asynchronously?

I don't know...it seems like the more I learn about AMD, the less it makes sense.

@nfriedly
Copy link

Now that jQuery is starting to support custom builds, perhaps both the global definition & the named module parts could be configured at build time?

That way 99% of jQuery users can keep the current behavior, but those of us that need a private jQuery can have a reliable, automated method of getting it.

I don't mind having an extra step in my JS build process as long as it can be automated. What I don't like is the current situation where I have to remember to manually edit a third-party library whenever I upgrade it to a new version. (And I especially don't like cases like this where the mistake won't be immediately apparent, so it's more likely to make it through QA and not get noticed until it starts breaking production websites.)

@dslatten
Copy link

@nfriedly +1

@jrburke
Copy link
Contributor

jrburke commented Jun 11, 2013

If you use another third party library that needs jQuery as a dependency, that dependency will likely be using 'jquery' as the dependency name.

This is only likely because it's hard-coded into jQuery itself! Otherwise, the most-likely choice would be the filename that jQuery is distributed by (minus the .js extension), because it requires the least amount of effort (e.g., path configuration or renaming files).

For third party libraries that depend on jQuery, they need a name to refer to jQuery. Backbone is a good example. In an AMD version of it, it would depend on 'jquery' and 'underscore'. It uses these top level IDs because it cannot possibly know the path to the file that is used in projects. All jQuery plugins in a modular world would use 'jquery' as the dependency name for similar reasons.

So, jQuery registering as 'jquery' is not an issue once you have one of those libraries in your project...

Based on my understanding, it's still an issue in at least 2 cases:

  1. Different versions of jQuery are needed within the same page (by different AMD modules that are unaware of the other's existence).
  2. Someone customizes a copy of jQuery, but doesn't change the part that defines it as the jquery module.

For both of those situations, they likely need full module namespace separation. Not just jQuery, but the plugins each one uses. It is unlikely the user would try to also rename all the modules used for the two different versions of jQuery to avoid conflicts. That is what the aforementioned multiversion support and namespace building provides. Complete namespace separation without needing to manually modify the source files.

...you should be placing jQuery under the 'jquery' modules ID. Projects that use a module ID like 'vendor/jquery-1.9.1' is an AMD anti-pattern.

So in an effort to encourage modular/reusable/interoperable code, AMD recommends a best practice that all-but-guarantees namespace collisions and interoperability limitations?

See above about the tools available to contain namespaces. Those are available without needing the actual modules to do anything special. Once IDs are strings, instead of globals, it allows those approaches.

The interop limitations stem from the uneven tech in browsers and a lack of a true module system that can give real, isolated spaces like the ES Module Loader API can.

So if I create an app according to the Asynchrous Module Definition specs, and I need my app to asynchronously load JavaScript modules (including jQuery), then my only option is to build a version that isn't modular and doesn't load asynchronously?

Please see the above multiversion and namespace builds as options for containment. They still allow for async loading.

I don't know...it seems like the more I learn about AMD, the less it makes sense.

Feel free to wait for ES modules. Nothing else today will do better though, and a lot of the same ideas will still be in play with ES modules. ES modules will have a much stronger isolation story though since it has the option to change the language and the browser.

All that said, I think your suggestion about getting some mechanism to avoid setting a global can be addressed, and I think that still may be worth pursuing separately in a targeted ticket.

For build options, I can see allowing options for those things, but for a standard build, I would like to see the module config support for skipping the global and the named jquery as the default options.

@nfriedly
Copy link

...for a standard build, I would like to see the module config support for skipping the global and the named jquery as the default options.

I forgot to mention it in my previous comment, but I'd be happy with this as well.

@timmywil
Copy link
Member

For build options, I can see allowing options for those things, but for a standard build, I would like to see the module config support for skipping the global and the named jquery as the default options.

This is a great idea.

@timmywil
Copy link
Member

I've opened a ticket for this. Please continue discussion there. http://bugs.jquery.com/ticket/14016

yamkazu referenced this pull request in yamkazu/gaiden Jun 20, 2014
diff --git a/src/dist/assets/scripts/angular-resource.js b/src/dist/assets/scripts/angular-resource.js
new file mode 100644
index 0000000..935800e
--- /dev/null
+++ b/src/dist/assets/scripts/angular-resource.js
@@ -0,0 +1,607 @@
+/**
+ * @license AngularJS v1.2.15
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {'use strict';
+
+var $resourceMinErr = angular.$$minErr('$resource');
+
+// Helper functions and regex to lookup a dotted path on an object
+// stopping at undefined/null.  The path must be composed of ASCII
+// identifiers (just like $parse)
+var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
+
+function isValidDottedPath(path) {
+  return (path != null && path !== '' && path !== 'hasOwnProperty' &&
+      MEMBER_NAME_REGEX.test('.' + path));
+}
+
+function lookupDottedPath(obj, path) {
+  if (!isValidDottedPath(path)) {
+    throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
+  }
+  var keys = path.split('.');
+  for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
+    var key = keys[i];
+    obj = (obj !== null) ? obj[key] : undefined;
+  }
+  return obj;
+}
+
+/**
+ * Create a shallow copy of an object and clear other fields from the destination
+ */
+function shallowClearAndCopy(src, dst) {
+  dst = dst || {};
+
+  angular.forEach(dst, function(value, key){
+    delete dst[key];
+  });
+
+  for (var key in src) {
+    if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+      dst[key] = src[key];
+    }
+  }
+
+  return dst;
+}
+
+/**
+ * @ngdoc module
+ * @name ngResource
+ * @description
+ *
+ * # ngResource
+ *
+ * The `ngResource` module provides interaction support with RESTful services
+ * via the $resource service.
+ *
+ *
+ * <div doc-module-components="ngResource"></div>
+ *
+ * See {@link ngResource.$resource `$resource`} for usage.
+ */
+
+/**
+ * @ngdoc service
+ * @name $resource
+ * @requires $http
+ *
+ * @description
+ * A factory which creates a resource object that lets you interact with
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
+ *
+ * The returned resource object has action methods which provide high-level behaviors without
+ * the need to interact with the low level {@link ng.$http $http} service.
+ *
+ * Requires the {@link ngResource `ngResource`} module to be installed.
+ *
+ * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
+ *   `/user/:username`. If you are using a URL with a port number (e.g.
+ *   `http://example.com:8080/api`), it will be respected.
+ *
+ *   If you are using a url with a suffix, just add the suffix, like this:
+ *   `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
+ *   or even `$resource('http://example.com/resource/:resource_id.:format')`
+ *   If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
+ *   collapsed down to a single `.`.  If you need this sequence to appear and not collapse then you
+ *   can escape it with `/\.`.
+ *
+ * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
+ *   `actions` methods. If any of the parameter value is a function, it will be executed every time
+ *   when a param value needs to be obtained for a request (unless the param was overridden).
+ *
+ *   Each key value in the parameter object is first bound to url template if present and then any
+ *   excess keys are appended to the url search query after the `?`.
+ *
+ *   Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
+ *   URL `/path/greet?salutation=Hello`.
+ *
+ *   If the parameter value is prefixed with `@` then the value of that parameter is extracted from
+ *   the data object (useful for non-GET operations).
+ *
+ * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend
+ *   the default set of resource actions. The declaration should be created in the format of {@link
+ *   ng.$http#usage_parameters $http.config}:
+ *
+ *       {action1: {method:?, params:?, isArray:?, headers:?, ...},
+ *        action2: {method:?, params:?, isArray:?, headers:?, ...},
+ *        ...}
+ *
+ *   Where:
+ *
+ *   - **`action`** – {string} – The name of action. This name becomes the name of the method on
+ *     your resource object.
+ *   - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`,
+ *     `DELETE`, and `JSONP`.
+ *   - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
+ *     the parameter value is a function, it will be executed every time when a param value needs to
+ *     be obtained for a request (unless the param was overridden).
+ *   - **`url`** – {string} – action specific `url` override. The url templating is supported just
+ *     like for the resource-level urls.
+ *   - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
+ *     see `returns` section.
+ *   - **`transformRequest`** –
+ *     `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
+ *     transform function or an array of such functions. The transform function takes the http
+ *     request body and headers and returns its transformed (typically serialized) version.
+ *   - **`transformResponse`** –
+ *     `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
+ *     transform function or an array of such functions. The transform function takes the http
+ *     response body and headers and returns its transformed (typically deserialized) version.
+ *   - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
+ *     GET request, otherwise if a cache instance built with
+ *     {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
+ *     caching.
+ *   - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
+ *     should abort the request when resolved.
+ *   - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
+ *     XHR object. See
+ *     [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
+ *     for more information.
+ *   - **`responseType`** - `{string}` - see
+ *     [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
+ *   - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
+ *     `response` and `responseError`. Both `response` and `responseError` interceptors get called
+ *     with `http response` object. See {@link ng.$http $http interceptors}.
+ *
+ * @returns {Object} A resource "class" object with methods for the default set of resource actions
+ *   optionally extended with custom `actions`. The default set contains these actions:
+ *   ```js
+ *   { 'get':    {method:'GET'},
+ *     'save':   {method:'POST'},
+ *     'query':  {method:'GET', isArray:true},
+ *     'remove': {method:'DELETE'},
+ *     'delete': {method:'DELETE'} };
+ *   ```
+ *
+ *   Calling these methods invoke an {@link ng.$http} with the specified http method,
+ *   destination and parameters. When the data is returned from the server then the object is an
+ *   instance of the resource class. The actions `save`, `remove` and `delete` are available on it
+ *   as  methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
+ *   read, update, delete) on server-side data like this:
+ *   ```js
+ *   var User = $resource('/user/:userId', {userId:'@id'});
+ *   var user = User.get({userId:123}, function() {
+ *     user.abc = true;
+ *     user.$save();
+ *   });
+ *   ```
+ *
+ *   It is important to realize that invoking a $resource object method immediately returns an
+ *   empty reference (object or array depending on `isArray`). Once the data is returned from the
+ *   server the existing reference is populated with the actual data. This is a useful trick since
+ *   usually the resource is assigned to a model which is then rendered by the view. Having an empty
+ *   object results in no rendering, once the data arrives from the server then the object is
+ *   populated with the data and the view automatically re-renders itself showing the new data. This
+ *   means that in most cases one never has to write a callback function for the action methods.
+ *
+ *   The action methods on the class object or instance object can be invoked with the following
+ *   parameters:
+ *
+ *   - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
+ *   - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
+ *   - non-GET instance actions:  `instance.$action([parameters], [success], [error])`
+ *
+ *   Success callback is called with (value, responseHeaders) arguments. Error callback is called
+ *   with (httpResponse) argument.
+ *
+ *   Class actions return empty instance (with additional properties below).
+ *   Instance actions return promise of the action.
+ *
+ *   The Resource instances and collection have these additional properties:
+ *
+ *   - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
+ *     instance or collection.
+ *
+ *     On success, the promise is resolved with the same resource instance or collection object,
+ *     updated with data from server. This makes it easy to use in
+ *     {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
+ *     rendering until the resource(s) are loaded.
+ *
+ *     On failure, the promise is resolved with the {@link ng.$http http response} object, without
+ *     the `resource` property.
+ *
+ *   - `$resolved`: `true` after first server interaction is completed (either with success or
+ *      rejection), `false` before that. Knowing if the Resource has been resolved is useful in
+ *      data-binding.
+ *
+ * @example
+ *
+ * # Credit card resource
+ *
+ * ```js
+     // Define CreditCard class
+     var CreditCard = $resource('/user/:userId/card/:cardId',
+      {userId:123, cardId:'@id'}, {
+       charge: {method:'POST', params:{charge:true}}
+      });
+
+     // We can retrieve a collection from the server
+     var cards = CreditCard.query(function() {
+       // GET: /user/123/card
+       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
+
+       var card = cards[0];
+       // each item is an instance of CreditCard
+       expect(card instanceof CreditCard).toEqual(true);
+       card.name = "J. Smith";
+       // non GET methods are mapped onto the instances
+       card.$save();
+       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
+       // server returns: {id:456, number:'1234', name: 'J. Smith'};
+
+       // our custom method is mapped as well.
+       card.$charge({amount:9.99});
+       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
+     });
+
+     // we can create an instance as well
+     var newCard = new CreditCard({number:'0123'});
+     newCard.name = "Mike Smith";
+     newCard.$save();
+     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
+     // server returns: {id:789, number:'0123', name: 'Mike Smith'};
+     expect(newCard.id).toEqual(789);
+ * ```
+ *
+ * The object returned from this function execution is a resource "class" which has "static" method
+ * for each action in the definition.
+ *
+ * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
+ * `headers`.
+ * When the data is returned from the server then the object is an instance of the resource type and
+ * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
+ * operations (create, read, update, delete) on server-side data.
+
+   ```js
+     var User = $resource('/user/:userId', {userId:'@id'});
+     User.get({userId:123}, function(user) {
+       user.abc = true;
+       user.$save();
+     });
+   ```
+ *
+ * It's worth noting that the success callback for `get`, `query` and other methods gets passed
+ * in the response that came from the server as well as $http header getter function, so one
+ * could rewrite the above example and get access to http headers as:
+ *
+   ```js
+     var User = $resource('/user/:userId', {userId:'@id'});
+     User.get({userId:123}, function(u, getResponseHeaders){
+       u.abc = true;
+       u.$save(function(u, putResponseHeaders) {
+         //u => saved user object
+         //putResponseHeaders => $http header getter
+       });
+     });
+   ```
+ *
+ * You can also access the raw `$http` promise via the `$promise` property on the object returned
+ *
+   ```
+     var User = $resource('/user/:userId', {userId:'@id'});
+     User.get({userId:123})
+         .$promise.then(function(user) {
+           $scope.user = user;
+         });
+   ```
+
+ * # Creating a custom 'PUT' request
+ * In this example we create a custom method on our resource to make a PUT request
+ * ```js
+ *		var app = angular.module('app', ['ngResource', 'ngRoute']);
+ *
+ *		// Some APIs expect a PUT request in the format URL/object/ID
+ *		// Here we are creating an 'update' method
+ *		app.factory('Notes', ['$resource', function($resource) {
+ *    return $resource('/notes/:id', null,
+ *        {
+ *            'update': { method:'PUT' }
+ *        });
+ *		}]);
+ *
+ *		// In our controller we get the ID from the URL using ngRoute and $routeParams
+ *		// We pass in $routeParams and our Notes factory along with $scope
+ *		app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
+                                      function($scope, $routeParams, Notes) {
+ *    // First get a note object from the factory
+ *    var note = Notes.get({ id:$routeParams.id });
+ *    $id = note.id;
+ *
+ *    // Now call update passing in the ID first then the object you are updating
+ *    Notes.update({ id:$id }, note);
+ *
+ *    // This will PUT /notes/ID with the note object in the request payload
+ *		}]);
+ * ```
+ */
+angular.module('ngResource', ['ng']).
+  factory('$resource', ['$http', '$q', function($http, $q) {
+
+    var DEFAULT_ACTIONS = {
+      'get':    {method:'GET'},
+      'save':   {method:'POST'},
+      'query':  {method:'GET', isArray:true},
+      'remove': {method:'DELETE'},
+      'delete': {method:'DELETE'}
+    };
+    var noop = angular.noop,
+        forEach = angular.forEach,
+        extend = angular.extend,
+        copy = angular.copy,
+        isFunction = angular.isFunction;
+
+    /**
+     * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
+     * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
+     * segments:
+     *    segment       = *pchar
+     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+     *    pct-encoded   = "%" HEXDIG HEXDIG
+     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+     *                     / "*" / "+" / "," / ";" / "="
+     */
+    function encodeUriSegment(val) {
+      return encodeUriQuery(val, true).
+        replace(/%26/gi, '&').
+        replace(/%3D/gi, '=').
+        replace(/%2B/gi, '+');
+    }
+
+
+    /**
+     * This method is intended for encoding *key* or *value* parts of query component. We need a
+     * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
+     * have to be encoded per http://tools.ietf.org/html/rfc3986:
+     *    query       = *( pchar / "/" / "?" )
+     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+     *    pct-encoded   = "%" HEXDIG HEXDIG
+     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+     *                     / "*" / "+" / "," / ";" / "="
+     */
+    function encodeUriQuery(val, pctEncodeSpaces) {
+      return encodeURIComponent(val).
+        replace(/%40/gi, '@').
+        replace(/%3A/gi, ':').
+        replace(/%24/g, '$').
+        replace(/%2C/gi, ',').
+        replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
+    }
+
+    function Route(template, defaults) {
+      this.template = template;
+      this.defaults = defaults || {};
+      this.urlParams = {};
+    }
+
+    Route.prototype = {
+      setUrlParams: function(config, params, actionUrl) {
+        var self = this,
+            url = actionUrl || self.template,
+            val,
+            encodedVal;
+
+        var urlParams = self.urlParams = {};
+        forEach(url.split(/\W/), function(param){
+          if (param === 'hasOwnProperty') {
+            throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
+          }
+          if (!(new RegExp("^\\d+$").test(param)) && param &&
+               (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
+            urlParams[param] = true;
+          }
+        });
+        url = url.replace(/\\:/g, ':');
+
+        params = params || {};
+        forEach(self.urlParams, function(_, urlParam){
+          val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
+          if (angular.isDefined(val) && val !== null) {
+            encodedVal = encodeUriSegment(val);
+            url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
+              return encodedVal + p1;
+            });
+          } else {
+            url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
+                leadingSlashes, tail) {
+              if (tail.charAt(0) == '/') {
+                return tail;
+              } else {
+                return leadingSlashes + tail;
+              }
+            });
+          }
+        });
+
+        // strip trailing slashes and set the url
+        url = url.replace(/\/+$/, '') || '/';
+        // then replace collapse `/.` if found in the last URL path segment before the query
+        // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
+        url = url.replace(/\/\.(?=\w+($|\?))/, '.');
+        // replace escaped `/\.` with `/.`
+        config.url = url.replace(/\/\\\./, '/.');
+
+
+        // set params - delegate param encoding to $http
+        forEach(params, function(value, key){
+          if (!self.urlParams[key]) {
+            config.params = config.params || {};
+            config.params[key] = value;
+          }
+        });
+      }
+    };
+
+
+    function resourceFactory(url, paramDefaults, actions) {
+      var route = new Route(url);
+
+      actions = extend({}, DEFAULT_ACTIONS, actions);
+
+      function extractParams(data, actionParams){
+        var ids = {};
+        actionParams = extend({}, paramDefaults, actionParams);
+        forEach(actionParams, function(value, key){
+          if (isFunction(value)) { value = value(); }
+          ids[key] = value && value.charAt && value.charAt(0) == '@' ?
+            lookupDottedPath(data, value.substr(1)) : value;
+        });
+        return ids;
+      }
+
+      function defaultResponseInterceptor(response) {
+        return response.resource;
+      }
+
+      function Resource(value){
+        shallowClearAndCopy(value || {}, this);
+      }
+
+      forEach(actions, function(action, name) {
+        var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
+
+        Resource[name] = function(a1, a2, a3, a4) {
+          var params = {}, data, success, error;
+
+          /* jshint -W086 */ /* (purposefully fall through case statements) */
+          switch(arguments.length) {
+          case 4:
+            error = a4;
+            success = a3;
+            //fallthrough
+          case 3:
+          case 2:
+            if (isFunction(a2)) {
+              if (isFunction(a1)) {
+                success = a1;
+                error = a2;
+                break;
+              }
+
+              success = a2;
+              error = a3;
+              //fallthrough
+            } else {
+              params = a1;
+              data = a2;
+              success = a3;
+              break;
+            }
+          case 1:
+            if (isFunction(a1)) success = a1;
+            else if (hasBody) data = a1;
+            else params = a1;
+            break;
+          case 0: break;
+          default:
+            throw $resourceMinErr('badargs',
+              "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
+              arguments.length);
+          }
+          /* jshint +W086 */ /* (purposefully fall through case statements) */
+
+          var isInstanceCall = this instanceof Resource;
+          var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
+          var httpConfig = {};
+          var responseInterceptor = action.interceptor && action.interceptor.response ||
+                                    defaultResponseInterceptor;
+          var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
+                                    undefined;
+
+          forEach(action, function(value, key) {
+            if (key != 'params' && key != 'isArray' && key != 'interceptor') {
+              httpConfig[key] = copy(value);
+            }
+          });
+
+          if (hasBody) httpConfig.data = data;
+          route.setUrlParams(httpConfig,
+                             extend({}, extractParams(data, action.params || {}), params),
+                             action.url);
+
+          var promise = $http(httpConfig).then(function(response) {
+            var data = response.data,
+                promise = value.$promise;
+
+            if (data) {
+              // Need to convert action.isArray to boolean in case it is undefined
+              // jshint -W018
+              if (angular.isArray(data) !== (!!action.isArray)) {
+                throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
+                  'response to contain an {0} but got an {1}',
+                  action.isArray?'array':'object', angular.isArray(data)?'array':'object');
+              }
+              // jshint +W018
+              if (action.isArray) {
+                value.length = 0;
+                forEach(data, function(item) {
+                  value.push(new Resource(item));
+                });
+              } else {
+                shallowClearAndCopy(data, value);
+                value.$promise = promise;
+              }
+            }
+
+            value.$resolved = true;
+
+            response.resource = value;
+
+            return response;
+          }, function(response) {
+            value.$resolved = true;
+
+            (error||noop)(response);
+
+            return $q.reject(response);
+          });
+
+          promise = promise.then(
+              function(response) {
+                var value = responseInterceptor(response);
+                (success||noop)(value, response.headers);
+                return value;
+              },
+              responseErrorInterceptor);
+
+          if (!isInstanceCall) {
+            // we are creating instance / collection
+            // - set the initial promise
+            // - return the instance / collection
+            value.$promise = promise;
+            value.$resolved = false;
+
+            return value;
+          }
+
+          // instance call
+          return promise;
+        };
+
+
+        Resource.prototype['$' + name] = function(params, success, error) {
+          if (isFunction(params)) {
+            error = success; success = params; params = {};
+          }
+          var result = Resource[name].call(this, params, this, success, error);
+          return result.$promise || result;
+        };
+      });
+
+      Resource.bind = function(additionalParamDefaults){
+        return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
+      };
+
+      return Resource;
+    }
+
+    return resourceFactory;
+  }]);
+
+
+})(window, window.angular);
diff --git a/src/dist/assets/scripts/angular-resource.min.js b/src/dist/assets/scripts/angular-resource.min.js
new file mode 100644
index 0000000..5c7c854
--- /dev/null
+++ b/src/dist/assets/scripts/angular-resource.min.js
@@ -0,0 +1,13 @@
+/*
+ AngularJS v1.2.15
+ (c) 2010-2014 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(H,a,A){'use strict';function D(p,g){g=g||{};a.forEach(g,function(a,c){delete g[c]});for(var c in p)!p.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(g[c]=p[c]);return g}var v=a.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;a.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(p,g){function c(a,c){this.template=a;this.defaults=c||{};this.urlParams={}}function t(n,w,l){function r(h,d){var e={};d=x({},w,d);s(d,function(b,d){u(b)&&(b=b());var k;if(b&&
+b.charAt&&"@"==b.charAt(0)){k=h;var a=b.substr(1);if(null==a||""===a||"hasOwnProperty"===a||!C.test("."+a))throw v("badmember",a);for(var a=a.split("."),f=0,c=a.length;f<c&&k!==A;f++){var g=a[f];k=null!==k?k[g]:A}}else k=b;e[d]=k});return e}function e(a){return a.resource}function f(a){D(a||{},this)}var F=new c(n);l=x({},B,l);s(l,function(h,d){var c=/^(POST|PUT|PATCH)$/i.test(h.method);f[d]=function(b,d,k,w){var q={},n,l,y;switch(arguments.length){case 4:y=w,l=k;case 3:case 2:if(u(d)){if(u(b)){l=
+b;y=d;break}l=d;y=k}else{q=b;n=d;l=k;break}case 1:u(b)?l=b:c?n=b:q=b;break;case 0:break;default:throw v("badargs",arguments.length);}var t=this instanceof f,m=t?n:h.isArray?[]:new f(n),z={},B=h.interceptor&&h.interceptor.response||e,C=h.interceptor&&h.interceptor.responseError||A;s(h,function(a,b){"params"!=b&&("isArray"!=b&&"interceptor"!=b)&&(z[b]=G(a))});c&&(z.data=n);F.setUrlParams(z,x({},r(n,h.params||{}),q),h.url);q=p(z).then(function(b){var d=b.data,k=m.$promise;if(d){if(a.isArray(d)!==!!h.isArray)throw v("badcfg",
+h.isArray?"array":"object",a.isArray(d)?"array":"object");h.isArray?(m.length=0,s(d,function(b){m.push(new f(b))})):(D(d,m),m.$promise=k)}m.$resolved=!0;b.resource=m;return b},function(b){m.$resolved=!0;(y||E)(b);return g.reject(b)});q=q.then(function(b){var a=B(b);(l||E)(a,b.headers);return a},C);return t?q:(m.$promise=q,m.$resolved=!1,m)};f.prototype["$"+d]=function(b,a,k){u(b)&&(k=a,a=b,b={});b=f[d].call(this,b,this,a,k);return b.$promise||b}});f.bind=function(a){return t(n,x({},w,a),l)};return f}
+var B={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},E=a.noop,s=a.forEach,x=a.extend,G=a.copy,u=a.isFunction;c.prototype={setUrlParams:function(c,g,l){var r=this,e=l||r.template,f,p,h=r.urlParams={};s(e.split(/\W/),function(a){if("hasOwnProperty"===a)throw v("badname");!/^\d+$/.test(a)&&(a&&RegExp("(^|[^\\\\]):"+a+"(\\W|$)").test(e))&&(h[a]=!0)});e=e.replace(/\\:/g,":");g=g||{};s(r.urlParams,function(d,c){f=g.hasOwnProperty(c)?
+g[c]:r.defaults[c];a.isDefined(f)&&null!==f?(p=encodeURIComponent(f).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),e=e.replace(RegExp(":"+c+"(\\W|$)","g"),function(a,c){return p+c})):e=e.replace(RegExp("(/?):"+c+"(\\W|$)","g"),function(a,c,d){return"/"==d.charAt(0)?d:c+d})});e=e.replace(/\/+$/,"")||"/";e=e.replace(/\/\.(?=\w+($|\?))/,".");c.url=e.replace(/\/\\\./,"/.");s(g,function(a,
+e){r.urlParams[e]||(c.params=c.params||{},c.params[e]=a)})}};return t}])})(window,window.angular);
+//# sourceMappingURL=angular-resource.min.js.map
diff --git a/src/dist/assets/scripts/angular-resource.min.js.map b/src/dist/assets/scripts/angular-resource.min.js.map
new file mode 100644
index 0000000..1766ea9
--- /dev/null
+++ b/src/dist/assets/scripts/angular-resource.min.js.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"angular-resource.min.js",
+"lineCount":12,
+"mappings":"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",
+"sources":["angular-resource.js"],
+"names":["window","angular","undefined","shallowClearAndCopy","src","dst","forEach","value","key","hasOwnProperty","charAt","$resourceMinErr","$$minErr","MEMBER_NAME_REGEX","module","factory","$http","$q","Route","template","defaults","urlParams","resourceFactory","url","paramDefaults","actions","extractParams","data","actionParams","ids","extend","isFunction","path","test","keys","split","i","ii","length","obj","defaultResponseInterceptor","response","resource","Resource","route","DEFAULT_ACTIONS","action","name","hasBody","method","a1","a2","a3","a4","params","success","error","arguments","isInstanceCall","isArray","httpConfig","responseInterceptor","interceptor","responseErrorInterceptor","responseError","copy","setUrlParams","promise","then","$promise","item","push","$resolved","noop","reject","headers","prototype","result","call","bind","Resource.bind","additionalParamDefaults","config","actionUrl","self","val","encodedVal","param","RegExp","replace","_","urlParam","isDefined","encodeURIComponent","match","p1","leadingSlashes","tail"]
+}
diff --git a/src/dist/assets/scripts/angular-route.min.js b/src/dist/assets/scripts/angular-route.min.js
new file mode 100644
index 0000000..c831a02
--- /dev/null
+++ b/src/dist/assets/scripts/angular-route.min.js
@@ -0,0 +1,14 @@
+/*
+ AngularJS v1.2.15
+ (c) 2010-2014 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(n,e,A){'use strict';function x(s,g,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,w){function y(){p&&(p.remove(),p=null);h&&(h.$destroy(),h=null);l&&(k.leave(l,function(){p=null}),p=l,l=null)}function v(){var b=s.current&&s.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=s.current;l=w(b,function(d){k.enter(d,null,l||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||g()});y()});h=d.scope=b;h.$emit("$viewContentLoaded");h.$eval(u)}else y()}
+var h,l,p,t=b.autoscroll,u=b.onload||"";a.$on("$routeChangeSuccess",v);v()}}}function z(e,g,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var w=e(c.contents());b.controller&&(f.$scope=a,f=g(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));w(a)}}}n=e.module("ngRoute",["ng"]).provider("$route",function(){function s(a,c){return e.extend(new (e.extend(function(){},
+{prototype:a})),c)}function g(a,e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},k=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;k.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&g(a,c));if(a){var b=
+"/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a},g(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,g,n,v,h){function l(){var d=p(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!u)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)u=!1,a.$broadcast("$routeChangeStart",
+d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(t(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?g.get(d):g.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=h.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=
+b,c=n.get(b,{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function p(){var a,b;e.forEach(k,function(f,k){var q;if(q=!b){var g=c.path();q=f.keys;var l={};if(f.regexp)if(g=f.regexp.exec(g)){for(var h=1,p=g.length;h<p;++h){var n=q[h-1],r="string"==typeof g[h]?decodeURIComponent(g[h]):
+g[h];n&&r&&(l[n.name]=r)}q=l}else q=null;else q=null;q=a=q}q&&(b=s(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&s(k[null],{params:{},pathParams:{}})}function t(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var u=!1,r={routes:k,reload:function(){u=!0;a.$evalAsync(l)}};a.$on("$locationChangeSuccess",l);return r}]});n.provider("$routeParams",
+function(){this.$get=function(){return{}}});n.directive("ngView",x);n.directive("ngView",z);x.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
+//# sourceMappingURL=angular-route.min.js.map
diff --git a/src/dist/assets/scripts/angular-route.min.js.map b/src/dist/assets/scripts/angular-route.min.js.map
new file mode 100644
index 0000000..3510eaf
--- /dev/null
+++ b/src/dist/assets/scripts/angular-route.min.js.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"angular-route.min.js",
+"lineCount":13,
+"mappings":"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",
+"sources":["angular-route.js"],
+"names":["window","angular","undefined","ngViewFactory","$route","$anchorScroll","$animate","link","scope","$element","attr","ctrl","$transclude","cleanupLastView","previousElement","remove","currentScope","$destroy","currentElement","leave","update","locals","current","isDefined","$template","newScope","$new","clone","enter","onNgViewEnter","autoScrollExp","$eval","$emit","onloadExp","autoscroll","onload","$on","ngViewFillContentFactory","$compile","$controller","html","contents","controller","$scope","controllerAs","data","children","ngRouteModule","module","provider","$RouteProvider","inherit","parent","extra","extend","pathRegExp","path","opts","insensitive","caseInsensitiveMatch","ret","keys","replace","_","slash","key","option","optional","star","push","regexp","RegExp","routes","when","this.when","route","redirectPath","length","substr","otherwise","this.otherwise","params","$get","$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce","updateRoute","next","parseRoute","last","$$route","equals","pathParams","reloadOnSearch","forceReload","copy","$broadcast","redirectTo","isString","interpolate","search","url","then","resolve","template","templateUrl","forEach","value","get","invoke","isFunction","getTrustedResourceUrl","loadedTemplateUrl","response","all","error","match","m","exec","on","i","len","val","decodeURIComponent","name","string","result","split","segment","segmentMatch","join","reload","$evalAsync","$RouteParamsProvider","this.$get","directive","$inject"]
+}
diff --git a/src/dist/assets/scripts/angular.min.js b/src/dist/assets/scripts/angular.min.js
new file mode 100644
index 0000000..1e819cb
--- /dev/null
+++ b/src/dist/assets/scripts/angular.min.js
@@ -0,0 +1,207 @@
+/*
+ AngularJS v1.2.15
+ (c) 2010-2014 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(Q,T,s){'use strict';function z(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.2.15/"+(b?b+"/":"")+a;for(c=1;c<arguments.length;c++)a=a+(1==c?"?":"&")+"p"+(c-1)+"="+encodeURIComponent("function"==typeof arguments[c]?arguments[c].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[c]?"undefined":"string"!=typeof arguments[c]?JSON.stringify(arguments[c]):arguments[c]);return Error(a)}}function $a(b){if(null==b||Aa(b))return!1;
+var a=b.length;return 1===b.nodeType&&a?!0:x(b)||M(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function r(b,a,c){var d;if(b)if(D(b))for(d in b)"prototype"==d||("length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d))||a.call(c,b[d],d);else if(b.forEach&&b.forEach!==r)b.forEach(a,c);else if($a(b))for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function Ob(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function Qc(b,
+a,c){for(var d=Ob(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function Pb(b){return function(a,c){b(c,a)}}function ab(){for(var b=ia.length,a;b;){b--;a=ia[b].charCodeAt(0);if(57==a)return ia[b]="A",ia.join("");if(90==a)ia[b]="0";else return ia[b]=String.fromCharCode(a+1),ia.join("")}ia.unshift("0");return ia.join("")}function Qb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function v(b){var a=b.$$hashKey;r(arguments,function(a){a!==b&&r(a,function(a,c){b[c]=a})});Qb(b,a);return b}function R(b){return parseInt(b,
+10)}function Rb(b,a){return v(new (v(function(){},{prototype:b})),a)}function B(){}function Ba(b){return b}function Y(b){return function(){return b}}function E(b){return"undefined"===typeof b}function u(b){return"undefined"!==typeof b}function W(b){return null!=b&&"object"===typeof b}function x(b){return"string"===typeof b}function ub(b){return"number"===typeof b}function Ma(b){return"[object Date]"===ta.call(b)}function M(b){return"[object Array]"===ta.call(b)}function D(b){return"function"===typeof b}
+function bb(b){return"[object RegExp]"===ta.call(b)}function Aa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Rc(b){return!(!b||!(b.nodeName||b.prop&&b.attr&&b.find))}function Sc(b,a,c){var d=[];r(b,function(b,f,g){d.push(a.call(c,b,f,g))});return d}function cb(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Na(b,a){var c=cb(b,a);0<=c&&b.splice(c,1);return a}function $(b,a){if(Aa(b)||b&&b.$evalAsync&&b.$watch)throw Oa("cpws");
+if(a){if(b===a)throw Oa("cpi");if(M(b))for(var c=a.length=0;c<b.length;c++)a.push($(b[c]));else{c=a.$$hashKey;r(a,function(b,c){delete a[c]});for(var d in b)a[d]=$(b[d]);Qb(a,c)}}else(a=b)&&(M(b)?a=$(b,[]):Ma(b)?a=new Date(b.getTime()):bb(b)?a=RegExp(b.source):W(b)&&(a=$(b,{})));return a}function Sb(b,a){a=a||{};for(var c in b)!b.hasOwnProperty(c)||"$"===c.charAt(0)&&"$"===c.charAt(1)||(a[c]=b[c]);return a}function ua(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;
+var c=typeof b,d;if(c==typeof a&&"object"==c)if(M(b)){if(!M(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ua(b[d],a[d]))return!1;return!0}}else{if(Ma(b))return Ma(a)&&b.getTime()==a.getTime();if(bb(b)&&bb(a))return b.toString()==a.toString();if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||Aa(b)||Aa(a)||M(a))return!1;c={};for(d in b)if("$"!==d.charAt(0)&&!D(b[d])){if(!ua(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c.hasOwnProperty(d)&&"$"!==d.charAt(0)&&a[d]!==s&&!D(a[d]))return!1;
+return!0}return!1}function Tb(){return T.securityPolicy&&T.securityPolicy.isActive||T.querySelector&&!(!T.querySelector("[ng-csp]")&&!T.querySelector("[data-ng-csp]"))}function db(b,a){var c=2<arguments.length?va.call(arguments,2):[];return!D(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,c.concat(va.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Tc(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)?c=
+s:Aa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&(a.$evalAsync&&a.$watch)&&(c="$SCOPE");return c}function oa(b,a){return"undefined"===typeof b?s:JSON.stringify(b,Tc,a?"  ":null)}function Ub(b){return x(b)?JSON.parse(b):b}function Pa(b){"function"===typeof b?b=!0:b&&0!==b.length?(b=O(""+b),b=!("f"==b||"0"==b||"false"==b||"no"==b||"n"==b||"[]"==b)):b=!1;return b}function fa(b){b=w(b).clone();try{b.empty()}catch(a){}var c=w("<div>").append(b).html();try{return 3===b[0].nodeType?O(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,
+function(a,b){return"<"+O(b)})}catch(d){return O(c)}}function Vb(b){try{return decodeURIComponent(b)}catch(a){}}function Wb(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.split("="),d=Vb(c[0]),u(d)&&(b=u(c[1])?Vb(c[1]):!0,a[d]?M(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Xb(b){var a=[];r(b,function(b,d){M(b)?r(b,function(b){a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))}):a.push(wa(d,!0)+(!0===b?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function vb(b){return wa(b,
+!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Uc(b,a){function c(a){a&&d.push(a)}var d=[b],e,f,g=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;r(g,function(a){g[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(r(b.querySelectorAll("."+a),c),r(b.querySelectorAll("."+
+a+"\\:"),c),r(b.querySelectorAll("["+a+"]"),c))});r(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,f=(b[2]||"").replace(/\s+/g,",")):r(a.attributes,function(b){!e&&g[b.name]&&(e=a,f=b.value)})}});e&&a(e,f?[f]:[])}function Yb(b,a){var c=function(){b=w(b);if(b.injector()){var c=b[0]===T?"document":fa(b);throw Oa("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=Zb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",
+function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(Q&&!d.test(Q.name))return c();Q.name=Q.name.replace(d,"");Ca.resumeBootstrap=function(b){r(b,function(b){a.push(b)});c()}}function eb(b,a){a=a||"_";return b.replace(Vc,function(b,d){return(d?a:"")+b.toLowerCase()})}function wb(b,a,c){if(!b)throw Oa("areq",a||"?",c||"required");return b}function Qa(b,a,c){c&&M(b)&&(b=b[b.length-1]);wb(D(b),a,"not a function, got "+(b&&"object"==typeof b?
+b.constructor.name||"Object":typeof b));return b}function xa(b,a){if("hasOwnProperty"===b)throw Oa("badname",a);}function $b(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&D(b)?db(e,b):b}function xb(b){var a=b[0];b=b[b.length-1];if(a===b)return w(a);var c=[a];do{a=a.nextSibling;if(!a)break;c.push(a)}while(a!==b);return w(c)}function Wc(b){var a=z("$injector"),c=z("ng");b=b.angular||(b.angular={});b.$$minErr=b.$$minErr||z;return b.module||
+(b.module=function(){var b={};return function(e,f,g){if("hasOwnProperty"===e)throw c("badname","module");f&&b.hasOwnProperty(e)&&(b[e]=null);return b[e]||(b[e]=function(){function b(a,d,e){return function(){c[e||"push"]([a,d,arguments]);return n}}if(!f)throw a("nomod",e);var c=[],d=[],l=b("$injector","invoke"),n={_invokeQueue:c,_runBlocks:d,requires:f,name:e,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:b("$provide","value"),constant:b("$provide",
+"constant","unshift"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:l,run:function(a){d.push(a);return this}};g&&l(g);return n}())}}())}function Xc(b){v(b,{bootstrap:Yb,copy:$,extend:v,equals:ua,element:w,forEach:r,injector:Zb,noop:B,bind:db,toJson:oa,fromJson:Ub,identity:Ba,isUndefined:E,isDefined:u,isString:x,isFunction:D,isObject:W,isNumber:ub,isElement:Rc,isArray:M,
+version:Yc,isDate:Ma,lowercase:O,uppercase:Da,callbacks:{counter:0},$$minErr:z,$$csp:Tb});Ra=Wc(Q);try{Ra("ngLocale")}catch(a){Ra("ngLocale",[]).provider("$locale",Zc)}Ra("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:$c});a.provider("$compile",ac).directive({a:ad,input:bc,textarea:bc,form:bd,script:cd,select:dd,style:ed,option:fd,ngBind:gd,ngBindHtml:hd,ngBindTemplate:id,ngClass:jd,ngClassEven:kd,ngClassOdd:ld,ngCloak:md,ngController:nd,ngForm:od,ngHide:pd,ngIf:qd,ngInclude:rd,
+ngInit:sd,ngNonBindable:td,ngPluralize:ud,ngRepeat:vd,ngShow:wd,ngStyle:xd,ngSwitch:yd,ngSwitchWhen:zd,ngSwitchDefault:Ad,ngOptions:Bd,ngTransclude:Cd,ngModel:Dd,ngList:Ed,ngChange:Fd,required:cc,ngRequired:cc,ngValue:Gd}).directive({ngInclude:Hd}).directive(yb).directive(dc);a.provider({$anchorScroll:Id,$animate:Jd,$browser:Kd,$cacheFactory:Ld,$controller:Md,$document:Nd,$exceptionHandler:Od,$filter:ec,$interpolate:Pd,$interval:Qd,$http:Rd,$httpBackend:Sd,$location:Td,$log:Ud,$parse:Vd,$rootScope:Wd,
+$q:Xd,$sce:Yd,$sceDelegate:Zd,$sniffer:$d,$templateCache:ae,$timeout:be,$window:ce,$$rAF:de,$$asyncCallback:ee})}])}function Sa(b){return b.replace(fe,function(a,b,d,e){return e?d.toUpperCase():d}).replace(ge,"Moz$1")}function zb(b,a,c,d){function e(b){var e=c&&b?[this.filter(b)]:[this],m=a,k,l,n,q,p,t;if(!d||null!=b)for(;e.length;)for(k=e.shift(),l=0,n=k.length;l<n;l++)for(q=w(k[l]),m?q.triggerHandler("$destroy"):m=!m,p=0,q=(t=q.children()).length;p<q;p++)e.push(Ea(t[p]));return f.apply(this,arguments)}
+var f=Ea.fn[b],f=f.$original||f;e.$original=f;Ea.fn[b]=e}function L(b){if(b instanceof L)return b;x(b)&&(b=aa(b));if(!(this instanceof L)){if(x(b)&&"<"!=b.charAt(0))throw Ab("nosel");return new L(b)}if(x(b)){var a=T.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);Bb(this,a.childNodes);w(T.createDocumentFragment()).append(this)}else Bb(this,b)}function Cb(b){return b.cloneNode(!0)}function Fa(b){fc(b);var a=0;for(b=b.childNodes||[];a<b.length;a++)Fa(b[a])}function gc(b,
+a,c,d){if(u(d))throw Ab("offargs");var e=ja(b,"events");ja(b,"handle")&&(E(a)?r(e,function(a,c){Db(b,c,a);delete e[c]}):r(a.split(" "),function(a){E(c)?(Db(b,a,e[a]),delete e[a]):Na(e[a]||[],c)}))}function fc(b,a){var c=b[fb],d=Ta[c];d&&(a?delete Ta[c].data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),gc(b)),delete Ta[c],b[fb]=s))}function ja(b,a,c){var d=b[fb],d=Ta[d||-1];if(u(c))d||(b[fb]=d=++he,d=Ta[d]={}),d[a]=c;else return d&&d[a]}function hc(b,a,c){var d=ja(b,"data"),e=u(c),f=!e&&
+u(a),g=f&&!W(a);d||g||ja(b,"data",d={});if(e)d[a]=c;else if(f){if(g)return d&&d[a];v(d,a)}else return d}function Eb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function gb(b,a){a&&b.setAttribute&&r(a.split(" "),function(a){b.setAttribute("class",aa((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+aa(a)+" "," ")))})}function hb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g,
+" ");r(a.split(" "),function(a){a=aa(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",aa(c))}}function Bb(b,a){if(a){a=a.nodeName||!u(a.length)||Aa(a)?[a]:a;for(var c=0;c<a.length;c++)b.push(a[c])}}function ic(b,a){return ib(b,"$"+(a||"ngController")+"Controller")}function ib(b,a,c){b=w(b);9==b[0].nodeType&&(b=b.find("html"));for(a=M(a)?a:[a];b.length;){for(var d=b[0],e=0,f=a.length;e<f;e++)if((c=b.data(a[e]))!==s)return c;b=w(d.parentNode||11===d.nodeType&&d.host)}}function jc(b){for(var a=
+0,c=b.childNodes;a<c.length;a++)Fa(c[a]);for(;b.firstChild;)b.removeChild(b.firstChild)}function kc(b,a){var c=jb[a.toLowerCase()];return c&&lc[b.nodeName]&&c}function ie(b,a){var c=function(c,e){c.preventDefault||(c.preventDefault=function(){c.returnValue=!1});c.stopPropagation||(c.stopPropagation=function(){c.cancelBubble=!0});c.target||(c.target=c.srcElement||T);if(E(c.defaultPrevented)){var f=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;f.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=
+function(){return c.defaultPrevented||!1===c.returnValue};var g=Sb(a[e||c.type]||[]);r(g,function(a){a.call(b,c)});8>=P?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Ga(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===s&&(c=b.$$hashKey=ab()):c=b;return a+":"+c}function Ua(b){r(b,this.put,this)}function mc(b){var a,c;"function"==
+typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(je,""),c=c.match(ke),r(c[1].split(le),function(b){b.replace(me,function(b,c,d){a.push(d)})})),b.$inject=a):M(b)?(c=b.length-1,Qa(b[c],"fn"),a=b.slice(0,c)):Qa(b,"fn",!0);return a}function Zb(b){function a(a){return function(b,c){if(W(b))r(b,Pb(a));else return a(b,c)}}function c(a,b){xa(a,"service");if(D(b)||M(b))b=n.instantiate(b);if(!b.$get)throw Va("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],
+c,d,f,h;r(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(x(a))for(c=Ra(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,f=0,h=d.length;f<h;f++){var g=d[f],m=n.get(g[0]);m[g[1]].apply(m,g[2])}else D(a)?b.push(n.invoke(a)):M(a)?b.push(n.invoke(a)):Qa(a,"module")}catch(l){throw M(a)&&(a=a[a.length-1]),l.message&&(l.stack&&-1==l.stack.indexOf(l.message))&&(l=l.m…
EranSch referenced this pull request in jl455/woocommerce-cardconnect Apr 6, 2015
Basic scaffold, includes basic frontend JS for CC tokenization and backend UI

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93f1361
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f620d0c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+   GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/cardconnect-payment-gateway.php b/cardconnect-payment-gateway.php
new file mode 100644
index 0000000..380eacd
--- /dev/null
+++ b/cardconnect-payment-gateway.php
@@ -0,0 +1,302 @@
+<?php
+/*
+Plugin Name: CardConnect Payment Gateway
+Plugin URI: http://sofcorp.com/
+Description: Accept credit card payments in your WooCommerce store!
+Version: 0.0.0 Alpha
+Author: SOF Inc <eran@sofcorp.com>
+Author URI: http://sofcorp.com
+
+	Copyright: © 2015 SOF Inc <eran@sofcorp.com>.
+	License: GNU General Public License v2
+	License URI: http://www.gnu.org/licenses/gpl-2.0.html
+*/
+
+if(!defined('ABSPATH')){
+	exit; // Exit if accessed directly
+}
+
+add_action('plugins_loaded', 'CardConnectPaymentGateway_init', 0);
+function CardConnectPaymentGateway_init(){
+
+	if(class_exists('CardConnectPaymentGateway') || !class_exists('WC_Payment_Gateway')){
+		return;
+	}
+
+	/**
+	 * Gateway class
+	 */
+	class CardConnectPaymentGateway extends WC_Payment_Gateway {
+
+		const CC_TEST_URL = 'fts.cardconnect.com:6443';
+		const CC_LIVE_URL = '???';
+
+		/**
+		 * Constructor for the gateway.
+		 */
+		public function __construct(){
+			$this->id = 'card_connect';
+			$this->icon = apply_filters('woocommerce_CardConnectPaymentGateway_icon', '');
+			$this->has_fields = true;
+			$this->method_title = __('CardConnect Payment Gateway', 'cardconnect-payment-gateway');
+			$this->method_description = __('Payment gateway for CardConnect Payment Processing', 'cardconnect-payment-gateway');
+			$this->supports = array(
+				'default_credit_card_form',
+				'refunds',
+				'products',
+				// 'subscriptions',
+				// 'subscription_cancellation',
+				// 'subscription_reactivation',
+				// 'subscription_suspension',
+				// 'subscription_amount_changes',
+				// 'subscription_payment_method_change',
+				// 'subscription_date_changes'
+			);
+			$this->view_transaction_url = '#';
+
+			// Load the form fields
+			$this->init_form_fields();
+
+			// Load the settings.
+			$this->init_settings();
+
+			// Load user options
+			$this->load_options();
+
+			// Instantiate SDK for CardConnect
+			$this->init_cc_sdk();
+
+			// Actions
+			add_action('wp_enqueue_scripts', array( $this, 'register_scripts'));
+			add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
+			add_action('woocommerce_thankyou_CardConnectPaymentGateway', array($this, 'thankyou_page'));
+		}
+
+		/**
+		 * Load SDK for communicating with CardConnect servers
+		 *
+		 * @return void
+		 */
+		protected function init_cc_sdk(){
+
+			// @TODO: Load CC SDK stuff here
+
+		}
+
+		/**
+		 * Load user options into class
+		 *
+		 * @return void
+		 */
+		protected function load_options(){
+
+			$this->enabled = $this->get_option('enabled');
+
+			$this->title = $this->get_option('title');
+			$this->description = $this->get_option('description');
+			$this->mode = $this->get_option('mode', 'capture');
+
+			$this->sandbox = $this->get_option('sandbox');
+
+			$env_key = $this->sandbox == 'no' ? 'production' : 'sandbox';
+			$this->api_credentials = array(
+				'mid' => $this->get_option("{$env_key}_mid"),
+				'user' => $this->get_option("{$env_key}_user"),
+				'pass' => $this->get_option("{$env_key}_mid"),
+			);
+
+		}
+
+		/**
+		 * Create form fields for the payment gateway
+		 *
+		 * @return void
+		 */
+		public function init_form_fields(){
+			$this->form_fields = array(
+				'enabled' => array(
+					'title' => __('Enable/Disable', 'woocommerce'),
+					'label' => __('Enable CardConnect Payments', 'woocommerce'),
+					'type' => 'checkbox',
+					'description' => '',
+					'default' => 'no'
+				),
+				'title' => array(
+					'title' => __('Title', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'),
+					'default' => __('Credit card', 'woocommerce'),
+					'desc_tip' => true
+				),
+				'description' => array(
+					'title' => __('Description', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('This controls the description which the user sees during checkout.', 'woocommerce'),
+					'default' => 'Payment secured by CardConnect.',
+					'desc_tip' => true
+				),
+				'mode' => array(
+					'title' => __('Payment Mode', 'woocommerce'),
+					'label' => __('Capture payment or only authorize it.', 'woocommerce'),
+					'type' => 'select',
+					'description' => __('Select <strong>Authorize Only</strong> if you prefer to manually approve each transaction in the CardConnect dashboard.', 'woocommerce'),
+					'default' => 'capture',
+					'options' => array(
+						'capture' => __('Capture Payment', 'woocommerce'),
+						'auth_only' => __('Authorize Only', 'woocommerce')
+					)
+				),
+				'sandbox' => array(
+					'title' => __('Sandbox', 'woocommerce'),
+					'label' => __('Enable Sandbox Mode', 'woocommerce'),
+					'type' => 'checkbox',
+					'description' => __('Place the payment gateway in sandbox mode using the sandbox authentication fields below (real payments will not be taken).', 'woocommerce'),
+					'default' => 'yes',
+					'desc_tip' => true
+				),
+				'sandbox_mid' => array(
+					'title' => __('Sandbox Merchant ID (MID)', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('Platform identifier required to be included in API calls from your ERP application to the CardConnect demo system.  Once your interface is live, you will be assigned a unique id from the processor.  During development and testing, you can use this test merchant id.', 'woocommerce'),
+					'default' => '496160873888',
+					'desc_tip' => true
+				),
+				'sandbox_user' => array(
+					'title' => __('Sandbox Username', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('This is the default information, you may use this or an alternative if provided by CardConnect', 'woocommerce'),
+					'default' => 'testing',
+					'desc_tip' => true
+				),
+				'sandbox_password' => array(
+					'title' => __('Sandbox Password', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('This is the default information, you may use this or an alternative if provided by CardConnect', 'woocommerce'),
+					'default' => 'testing123',
+					'desc_tip' => true
+				),
+				'production_mid' => array(
+					'title' => __('Merchant ID (MID)', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('Your unique MID from CardConnect.', 'woocommerce'),
+					'default' => '',
+					'desc_tip' => true
+				),
+				'production_user' => array(
+					'title' => __('Username', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('Enter the credentials obtained from CardConnect', 'woocommerce'),
+					'default' => '',
+					'desc_tip' => true
+				),
+				'production_password' => array(
+					'title' => __('Password', 'woocommerce'),
+					'type' => 'text',
+					'description' => __('Enter the credentials obtained from CardConnect', 'woocommerce'),
+					'default' => '',
+					'desc_tip' => true
+				),
+			);
+		}
+
+		/**
+		 * Process the order payment status
+		 *
+		 * @param int $order_id
+		 *
+		 * @return array
+		 */
+		public function process_payment($order_id){
+			global $woocommerce;
+			$order = new WC_Order($order_id);
+
+			$token = isset( $_POST['card_connect_token'] ) ? wc_clean( $_POST['card_connect_token'] ) : false;
+
+			if(!$token){
+				wc_add_notice(__('Payment error:', 'woothemes') . 'Please make sure your card details have been entered correctly and that your browser supports JavaScript.', 'error');
+				return;
+			}
+
+			 // Payment Failed!
+			 $order->update_status('failed', __('Payment Failed', 'cardconnect-payment-gateway'));
+			 wc_add_notice( __('Payment error:', 'woothemes') . 'Card Error', 'error' );
+			 return;
+
+			$order->payment_complete();
+
+			// Reduce stock levels
+			$order->reduce_order_stock();
+
+			// Remove cart
+			$woocommerce->cart->empty_cart();
+
+			// Return thankyou redirect
+			return array(
+				'result' => 'success',
+				'redirect' => $this->get_return_url($order)
+			);
+		}
+
+		/**
+		 * Output for the order received page.
+		 *
+		 * @return void
+		 */
+		public function thankyou(){
+			if($description = $this->get_description()){
+				echo wpautop(wptexturize(wp_kses_post($description)));
+			}
+
+			echo '<h2>' . __('Our Details', 'cardconnect-payment-gateway') . '</h2>';
+
+			echo '<ul class="order_details CardConnectPaymentGateway_details">';
+
+			$fields = apply_filters('woocommerce_CardConnectPaymentGateway_fields', array(
+				'example_field' => __('Example field', 'cardconnect-payment-gateway')
+			));
+
+			foreach($fields as $key => $value){
+				if(!empty($this->$key)){
+					echo '<li class="' . esc_attr($key) . '">' . esc_attr($value) . ': <strong>' . wptexturize($this->$key) . '</strong></li>';
+				}
+			}
+
+			echo '</ul>';
+		}
+
+		/**
+		 * Output for the order received page.
+		 *
+		 * @return void
+		 */
+		public function payment_fields(){
+			wp_enqueue_script('woocommerce-cardconnect');
+			$this->credit_card_form(array(
+				'fields_have_names' => false
+			));
+		}
+
+		/**
+		 * Register Frontend Assets
+		 **/
+		public function register_scripts(){
+			wp_register_script(
+				'woocommerce-cardconnect',
+				plugins_url('javascript/dist/woocommerce-cc-gateway.js', __FILE__),
+				array('jquery'), '1.0', true
+			);
+		}
+
+	}
+
+	/**
+	 * Add the Gateway to WooCommerce
+	 **/
+	add_filter('woocommerce_payment_gateways', 'woocommerce_add_gateway_CardConnectPaymentGateway');
+	function woocommerce_add_gateway_CardConnectPaymentGateway($methods){
+		$methods[] = 'CardConnectPaymentGateway';
+
+		return $methods;
+	}
+
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..58348aa
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,54 @@
+var gulp = require('gulp');
+var browserify = require('browserify');
+var source = require('vinyl-source-stream');
+var watchify = require('watchify');
+
+gulp.task('browserify', function(){
+  browserifyShare();
+});
+
+gulp.task('browserifyTests', browserifyTests);
+
+function browserifyShare(){
+  // you need to pass these three config option to browserify
+  var b = browserify({
+    cache: {},
+    packageCache: {},
+    fullPaths: true
+  });
+  b = watchify(b);
+  b.on('update', function(){
+    bundleShare(b);
+  });
+
+  b.add('./javascript/src/woocommerce-cc-gateway.js');
+  bundleShare(b);
+}
+
+function bundleShare(b){
+  b.bundle()
+    .pipe(source('woocommerce-cc-gateway.js'))
+    .pipe(gulp.dest('./javascript/dist'));
+}
+
+function browserifyTests(){
+  // you need to pass these three config option to browserify
+  var b = browserify({
+    cache: {},
+    packageCache: {},
+    fullPaths: true
+  });
+  b = watchify(b);
+  b.on('update', function(){
+    bundleTests(b);
+  });
+
+  b.add('./javascript/spec/woocommerce-card-connect.spec.js');
+  bundleTests(b);
+}
+
+function bundleTests(b){
+  b.bundle()
+    .pipe(source('woocommerce-card-connect.spec.js'))
+    .pipe(gulp.dest('./javascript/spec/bundle'));
+}
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..24dc103
--- /dev/null
+++ b/index.php
@@ -0,0 +1 @@
+<?php //Silence is golden
diff --git a/javascript/dist/woocommerce-cc-gateway.js b/javascript/dist/woocommerce-cc-gateway.js
new file mode 100644
index 0000000..a15e11c
--- /dev/null
+++ b/javascript/dist/woocommerce-cc-gateway.js
@@ -0,0 +1,72 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"./javascript/src/woocommerce-cc-gateway.js":[function(require,module,exports){
+var woocommerce_card_connect_1 = require("./woocommerce-card-connect");
+jQuery(function ($) {
+    var cc = new woocommerce_card_connect_1.default($);
+    var $form = $('form.checkout');
+    function formSubmit(ev) {
+        ev.preventDefault();
+        ev.stopPropagation();
+        if (0 === $('input.card-connect-token').size()) {
+            var creditCard = $form.find('.wc-credit-card-form-card-number').val();
+            if (!creditCard) {
+                alert('Please enter a credit card number');
+                return false;
+            }
+            cc.getToken(creditCard, function (token, error) {
+                if (error)
+                    alert(error);
+                $('<input />')
+                    .attr('name', 'card_connect_token')
+                    .attr('type', 'hidden')
+                    .addClass('card-connect-token')
+                    .val(token)
+                    .appendTo($form);
+                $form.submit();
+            });
+            return false;
+        }
+        return true;
+    }
+    $form.on('checkout_place_order_card_connect', formSubmit);
+    $('body').on('checkout_error', function () { return $('.card-connect-token').remove(); });
+    $('form.checkout').on('change', '.wc-credit-card-form-card-number', function () {
+        $('.card-connect-token').remove();
+    });
+});
+
+},{"./woocommerce-card-connect":"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/javascript/src/woocommerce-card-connect.js"}],"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/javascript/src/woocommerce-card-connect.js":[function(require,module,exports){
+var WoocommereCardConnect = (function () {
+    function WoocommereCardConnect(jQuery) {
+        var _this = this;
+        this.baseUrl = 'https://fts.prinpay.com/cardsecure/cs?action=CE&type=json';
+        this.getToken = function (number, callback) {
+            if (!_this.validateCard(number))
+                return callback(null, 'Invalid Credit Card Number');
+            _this.$.get(_this.baseUrl + "&data=" + _this.cardNumber)
+                .done(function (data) { return _this.processRequest(data, callback); })
+                .fail(function (data) { return _this.failedRequest(data, callback); });
+        };
+        this.validateCard = function (number) {
+            _this.cardNumber = number;
+            return _this.cardNumber.length > 0;
+        };
+        this.processRequest = function (data, callback) {
+            var processToken = function (response) {
+                var action = response.action, data = response.data;
+                if (action === 'CE')
+                    callback(data, null);
+                else
+                    callback(null, data);
+            };
+            eval(data);
+        };
+        this.failedRequest = function (data, callback) {
+            return callback(null, 'Failed to connect to server');
+        };
+        this.$ = jQuery;
+    }
+    return WoocommereCardConnect;
+})();
+exports.default = WoocommereCardConnect;
+
+},{}]},{},["./javascript/src/woocommerce-cc-gateway.js"]);
diff --git a/javascript/spec/bundle/woocommerce-card-connect.spec.js b/javascript/spec/bundle/woocommerce-card-connect.spec.js
new file mode 100644
index 0000000..474089c
--- /dev/null
+++ b/javascript/spec/bundle/woocommerce-card-connect.spec.js
@@ -0,0 +1,16036 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"./javascript/spec/woocommerce-card-connect.spec.js":[function(require,module,exports){
+var woocommerce_card_connect_1 = require("../src/woocommerce-card-connect");
+var chai = require("chai");
+var expect = chai.expect;
+var $ = require('jquery');
+var cc = new woocommerce_card_connect_1.default($);
+describe('CardConnect Token Generator', function () {
+    it("should generate token with valid credit card number", function (done) {
+        cc.getToken('4242424242424242', function (token, error) {
+            expect(error).to.be.null;
+            expect(token).to.be.a('string');
+            expect(token).to.equal('9428873934894242');
+            done();
+        });
+    });
+    it("should generate error with non-numerical entry", function (done) {
+        cc.getToken('frisbee', function (token, error) {
+            expect(token).to.be.null;
+            expect(error).to.equal('0008::Data not decimal digits');
+            done();
+        });
+    });
+    it("should generate error with oversized entry", function (done) {
+        cc.getToken('42424242424242424242424242424242', function (token, error) {
+            expect(token).to.be.null;
+            expect(error).to.equal('0013::Data too long');
+            done();
+        });
+    });
+    it("should generate error when no entry made", function (done) {
+        cc.getToken('', function (token, error) {
+            expect(token).to.be.null;
+            expect(error).to.equal('Invalid Credit Card Number');
+            done();
+        });
+    });
+});
+
+},{"../src/woocommerce-card-connect":"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/javascript/src/woocommerce-card-connect.js","chai":"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/node_modules/chai/index.js","jquery":"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/node_modules/jquery/dist/jquery.js"}],"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/javascript/src/woocommerce-card-connect.js":[function(require,module,exports){
+var WoocommereCardConnect = (function () {
+    function WoocommereCardConnect(jQuery) {
+        var _this = this;
+        this.baseUrl = 'https://fts.prinpay.com/cardsecure/cs?action=CE&type=json';
+        this.getToken = function (number, callback) {
+            if (!_this.validateCard(number))
+                return callback(null, 'Invalid Credit Card Number');
+            _this.$.get(_this.baseUrl + "&data=" + _this.cardNumber)
+                .done(function (data) { return _this.processRequest(data, callback); })
+                .fail(function (data) { return _this.failedRequest(data, callback); });
+        };
+        this.validateCard = function (number) {
+            _this.cardNumber = number;
+            return _this.cardNumber.length > 0;
+        };
+        this.processRequest = function (data, callback) {
+            var processToken = function (response) {
+                var action = response.action, data = response.data;
+                if (action === 'CE')
+                    callback(data, null);
+                else
+                    callback(null, data);
+            };
+            eval(data);
+        };
+        this.failedRequest = function (data, callback) {
+            return callback(null, 'Failed to connect to server');
+        };
+        this.$ = jQuery;
+    }
+    return WoocommereCardConnect;
+})();
+exports.default = WoocommereCardConnect;
+
+},{}],"/Users/eran/Repositories/VVV/www/cardconnect/htdocs/wp-content/plugins/cardconnect-payment-gateway/node_modules/browserify/node_modules/buffer/index.js":[function(require,module,exports){
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license  MIT
+ */
+
+var base64 = require('base64-js')
+var ieee754 = require('ieee754')
+var isArray = require('is-array')
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+Buffer.poolSize = 8192 // not used by this implementation
+
+var kMaxLength = 0x3fffffff
+var rootParent = {}
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ *   === true    Use Uint8Array implementation (fastest)
+ *   === false   Use Object implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * Note:
+ *
+ * - Implementation must support adding new properties to `Uint8Array` instances.
+ *   Firefox 4-29 lacked support, fixed in Firefox 30+.
+ *   See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
+ *
+ *  - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
+ *
+ *  - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
+ *    incorrect length in some situations.
+ *
+ * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they will
+ * get the Object implementation, which is slower but will work correctly.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = (function () {
+  try {
+    var buf = new ArrayBuffer(0)
+    var arr = new Uint8Array(buf)
+    arr.foo = function () { return 42 }
+    return arr.foo() === 42 && // typed array instances can be augmented
+        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
+        new Uint8Array(1).subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
+  } catch (e) {
+    return false
+  }
+})()
+
+/**
+ * Class: Buffer
+ * =============
+ *
+ * The Buffer constructor returns instances of `Uint8Array` that are augmented
+ * with function properties for all the node `Buffer` API functions. We use
+ * `Uint8Array` so that square bracket notation works as expected -- it returns
+ * a single octet.
+ *
+ * By augmenting the instances, we can avoid modifying the `Uint8Array`
+ * prototype.
+ */
+function Buffer (subject, encoding) {
+  var self = this
+  if (!(self instanceof Buffer)) return new Buffer(subject, encoding)
+
+  var type = typeof subject
+  var length
+
+  if (type === 'number') {
+    length = +subject
+  } else if (type === 'string') {
+    length = Buffer.byteLength(subject, encoding)
+  } else if (type === 'object' && subject !== null) {
+    // assume object is array-like
+    if (subject.type === 'Buffer' && isArray(subject.data)) subject = subject.data
+    length = +subject.length
+  } else {
+    throw new TypeError('must start with number, buffer, array or string')
+  }
+
+  if (length > kMaxLength) {
+    throw new RangeError('Attempt to allocate Buffer larger than maximum size: 0x' +
+      kMaxLength.toString(16) + ' bytes')
+  }
+
+  if (length < 0) length = 0
+  else length >>>= 0 // coerce to uint32
+
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    // Preferred: Return an augmented `Uint8Array` instance for best performance
+    self = Buffer._augment(new Uint8Array(length)) // eslint-disable-line consistent-this
+  } else {
+    // Fallback: Return THIS instance of Buffer (created by `new`)
+    self.length = length
+    self._isBuffer = true
+  }
+
+  var i
+  if (Buffer.TYPED_ARRAY_SUPPORT && typeof subject.byteLength === 'number') {
+    // Speed optimization -- use set if we're copying from a typed array
+    self._set(subject)
+  } else if (isArrayish(subject)) {
+    // Treat array-ish objects as a byte array
+    if (Buffer.isBuffer(subject)) {
+      for (i = 0; i < length; i++) {
+        self[i] = subject.readUInt8(i)
+      }
+    } else {
+      for (i = 0; i < length; i++) {
+        self[i] = ((subject[i] % 256) + 256) % 256
+      }
+    }
+  } else if (type === 'string') {
+    self.write(subject, 0, encoding)
+  } else if (type === 'number' && !Buffer.TYPED_ARRAY_SUPPORT) {
+    for (i = 0; i < length; i++) {
+      self[i] = 0
+    }
+  }
+
+  if (length > 0 && length <= Buffer.poolSize) self.parent = rootParent
+
+  return self
+}
+
+function SlowBuffer (subject, encoding) {
+  if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding)
+
+  var buf = new Buffer(subject, encoding)
+  delete buf.parent
+  return buf
+}
+
+Buffer.isBuffer = function isBuffer (b) {
+  return !!(b != null && b._isBuffer)
+}
+
+Buffer.compare = function compare (a, b) {
+  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+    throw new TypeError('Arguments must be Buffers')
+  }
+
+  if (a === b) return 0
+
+  var x = a.length
+  var y = b.length
+  for (var i = 0, len = Math.min(x, y); i < len && a[i] === b[i]; i++) {}
+  if (i !== len) {
+    x = a[i]
+    y = b[i]
+  }
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+Buffer.isEncoding = function isEncoding (encoding) {
+  switch (String(encoding).toLowerCase()) {
+    case 'hex':
+    case 'utf8':
+    case 'utf-8':
+    case 'ascii':
+    case 'binary':
+    case 'base64':
+    case 'raw':
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      return true
+    default:
+      return false
+  }
+}
+
+Buffer.concat = function concat (list, totalLength) {
+  if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.')
+
+  if (list.length === 0) {
+    return new Buffer(0)
+  } else if (list.length === 1) {
+    return list[0]
+  }
+
+  var i
+  if (totalLength === undefined) {
+    totalLength = 0
+    for (i = 0; i < list.length; i++) {
+      totalLength += list[i].length
+    }
+  }
+
+  var buf = new Buffer(totalLength)
+  var pos = 0
+  for (i = 0; i < list.length; i++) {
+    var item = list[i]
+    item.copy(buf, pos)
+    pos += item.length
+  }
+  return buf
+}
+
+Buffer.byteLength = function byteLength (str, encoding) {
+  var ret
+  str = str + ''
+  switch (encoding || 'utf8') {
+    case 'ascii':
+    case 'binary':
+    case 'raw':
+      ret = str.length
+      break
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      ret = str.length * 2
+      break
+    case 'hex':
+      ret = str.length >>> 1
+      break
+    case 'utf8':
+    case 'utf-8':
+      ret = utf8ToBytes(str).length
+      break
+    case 'base64':
+      ret = base64ToBytes(str).length
+      break
+    default:
+      ret = str.length
+  }
+  return ret
+}
+
+// pre-set for values that may exist in the future
+Buffer.prototype.length = undefined
+Buffer.prototype.parent = undefined
+
+// toString(encoding, start=0, end=buffer.length)
+Buffer.prototype.toString = function toString (encoding, start, end) {
+  var loweredCase = false
+
+  start = start >>> 0
+  end = end === undefined || end === Infinity ? this.length : end >>> 0
+
+  if (!encoding) encoding = 'utf8'
+  if (start < 0) start = 0
+  if (end > this.length) end = this.length
+  if (end <= start) return ''
+
+  while (true) {
+    switch (encoding) {
+      case 'hex':
+        return hexSlice(this, start, end)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Slice(this, start, end)
+
+      case 'ascii':
+        return asciiSlice(this, start, end)
+
+      case 'binary':
+        return binarySlice(this, start, end)
+
+      case 'base64':
+        return base64Slice(this, start, end)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return utf16leSlice(this, start, end)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = (encoding + '').toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+Buffer.prototype.equals = function equals (b) {
+  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+  if (this === b) return true
+  return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function inspect () {
+  var str = ''
+  var max = exports.INSPECT_MAX_BYTES
+  if (this.length > 0) {
+    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
+    if (this.length > max) str += ' ... '
+  }
+  return '<Buffer ' + str + '>'
+}
+
+Buffer.prototype.compare = function compare (b) {
+  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+  if (this === b) return 0
+  return Buffer.compare(this, b)
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset) {
+  if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff
+  else if (byteOffset < -0x80000000) byteOffset = -0x80000000
+  byteOffset >>= 0
+
+  if (this.length === 0) return -1
+  if (byteOffset >= this.length) return -1
+
+  // Negative offsets start from the end of the buffer
+  if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)
+
+  if (typeof val === 'string') {
+    if (val.length === 0) return -1 // special case: looking for empty string always fails
+    return String.prototype.indexOf.call(this, val, byteOffset)
+  }
+  if (Buffer.isBuffer(val)) {
+    return arrayIndexOf(this, val, byteOffset)
+  }
+  if (typeof val === 'number') {
+    if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {
+      return Uint8Array.prototype.indexOf.call(this, val, byteOffset)
+    }
+    return arrayIndexOf(this, [ val ], byteOffset)
+  }
+
+  function arrayIndexOf (arr, val, byteOffset) {
+    var foundIndex = -1
+    for (var i = 0; byteOffset + i < arr.length; i++) {
+      if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) {
+        if (foundIndex === -1) foundIndex = i
+        if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex
+      } else {
+        foundIndex = -1
+      }
+    }
+    return -1
+  }
+
+  throw new TypeError('val must be string, number or Buffer')
+}
+
+// `get` will be removed in Node 0.13+
+Buffer.prototype.get = function get (offset) {
+  console.log('.get() is deprecated. Access using array indexes instead.')
+  return this.readUInt8(offset)
+}
+
+// `set` will be removed in Node 0.13+
+Buffer.prototype.set = function set (v, offset) {
+  console.log('.set() is deprecated. Access using array indexes instead.')
+  return this.writeUInt8(v, offset)
+}
+
+function hexWrite (buf, string, offset, length) {
+  offset = Number(offset) || 0
+  var remaining = buf.length - offset
+  if (!length) {
+    length = remaining
+  } else {
+    length = Number(length)
+    if (length > remaining) {
+      length = remaining
+    }
+  }
+
+  // must be an even number of digits
+  var strLen = string.length
+  if (strLen % 2 !== 0) throw new Error('Invalid hex string')
+
+  if (length > strLen / 2) {
+    length = strLen / 2
+  }
+  for (var i = 0; i < length; i++) {
+    var parsed = parseInt(string.substr(i * 2, 2), 16)
+    if (isNaN(parsed)) throw new Error('Invalid hex string')
+    buf[offset + i] = parsed
+  }
+  return i
+}
+
+function utf8Write (buf, string, offset, length) {
+  var charsWritten = blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+  return charsWritten
+}
+
+function asciiWrite (buf, string, offset, length) {
+  var charsWritten = blitBuffer(asciiToBytes(string), buf, offset, length)
+  return charsWritten
+}
+
+function binaryWrite (buf, string, offset, length) {
+  return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+  var charsWritten = blitBuffer(base64ToBytes(string), buf, offset, length)
+  return charsWritten
+}
+
+function utf16leWrite (buf, string, offset, length) {
+  var charsWritten = blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
+  return charsWritten
+}
+
+Buffer.prototype.write = function write (string, offset, length, encoding) {
+  // Support both (string, offset, length, encoding)
+  // and the legacy (string, encoding, offset, length)
+  if (isFinite(offset)) {
+    if (!isFinite(length)) {
+      encoding = length
+      length = undefined
+    }
+  } else {  // legacy
+    var swap = encoding
+    encoding = offset
+    offset = length
+    length = swap
+  }
+
+  offset = Number(offset) || 0
+
+  if (length < 0 || offset < 0 || offset > this.length) {
+    throw new RangeError('attempt to write outside buffer bounds')
+  }
+
+  var remaining = this.length - offset
+  if (!length) {
+    length = remaining
+  } else {
+    length = Number(length)
+    if (length > remaining) {
+      length = remaining
+    }
+  }
+  encoding = String(encoding || 'utf8').toLowerCase()
+
+  var ret
+  switch (encoding) {
+    case 'hex':
+      ret = hexWrite(this, string, offset, length)
+      break
+    case 'utf8':
+    case 'utf-8':
+      ret = utf8Write(this, string, offset, length)
+      break
+    case 'ascii':
+      ret = asciiWrite(this, string, offset, length)
+      break
+    case 'binary':
+      ret = binaryWrite(this, string, offset, length)
+      break
+    case 'base64':
+      ret = base64Write(this, string, offset, length)
+      break
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      ret = utf16leWrite(this, string, offset, length)
+      break
+    default:
+      throw new TypeError('Unknown encoding: ' + encoding)
+  }
+  return ret
+}
+
+Buffer.prototype.toJSON = function toJSON () {
+  return {
+    type: 'Buffer',
+    data: Array.prototype.slice.call(this._arr || this, 0)
+  }
+}
+
+function base64Slice (buf, start, end) {
+  if (start === 0 && end === buf.length) {
+    return base64.fromByteArray(buf)
+  } else {
+    return base64.fromByteArray(buf.slice(start, end))
+  }
+}
+
+function utf8Slice (buf, start, end) {
+  var res = ''
+  var tmp = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; i++) {
+    if (buf[i] <= 0x7F) {
+      res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
+      tmp = ''
+    } else {
+      tmp += '%' + buf[i].toString(16)
+    }
+  }
+
+  return res + decodeUtf8Char(tmp)
+}
+
+function asciiSlice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; i++) {
+    ret += String.fromCharCode(buf[i] & 0x7F)
+  }
+  return ret
+}
+
+function binarySlice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; i++) {
+    ret += String.fromCharCode(buf[i])
+  }
+  return ret
+}
+
+function hexSlice (buf, start, end) {
+  var len = buf.length
+
+  if (!start || start < 0) start = 0
+  if (!end || end < 0 || end > len) end = len
+
+  var out = ''
+  for (var i = start; i < end; i++) {
+    out += toHex(buf[i])
+  }
+  return out
+}
+
+function utf16leSlice (buf, start, end) {
+  var bytes = buf.slice(start, end)
+  var res = ''
+  for (var i = 0; i < bytes.length; i += 2) {
+    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
+  }
+  return res
+}
+
+Buffer.prototype.slice = function slice (start, end) {
+  var len = this.length
+  start = ~~start
+  end = end === undefined ? len : ~~end
+
+  if (start < 0) {
+    start += len
+    if (start < 0) start = 0
+  } else if (start > len) {
+    start = len
+  }
+
+  if (end < 0) {
+    end += len
+    if (end < 0) end = 0
+  } else if (end > len) {
+    end = len
+  }
+
+  if (end < start) end = start
+
+  var newBuf
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    newBuf = Buffer._augment(this.subarray(start, end))
+  } else {
+    var sliceLen = end - start
+    newBuf = new Buffer(sliceLen, undefined)
+    for (var i = 0; i < sliceLen; i++) {
+      newBuf[i] = this[i + start]
+    }
+  }
+
+  if (newBuf.length) newBuf.parent = this.parent || this
+
+  return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
+  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    checkOffset(offset, byteLength, this.length)
+  }
+
+  var val = this[offset + --byteLength]
+  var mul = 1
+  while (byteLength > 0 && (mul *= 0x100)) {
+    val += this[offset + --byteLength] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return ((this[offset]) |
+      (this[offset + 1] << 8) |
+      (this[offset + 2] << 16)) +
+      (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] * 0x1000000) +
+    ((this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var i = byteLength
+  var mul = 1
+  var val = this[offset + --i]
+  while (i > 0 && (mul *= 0x100)) {
+    val += this[offset + --i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  if (!(this[offset] & 0x80)) return (this[offset])
+  return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset] | (this[offset + 1] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset + 1] | (this[offset] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset]) |
+    (this[offset + 1] << 8) |
+    (this[offset + 2] << 16) |
+    (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] << 24) |
+    (this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+  if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')
+  if (value > max || value < min) throw new RangeError('value is out of bounds')
+  if (offset + ext > buf.length) throw new RangeError('index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+
+  var mul = 1
+  var i = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) >>> 0 & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+
+  var i = byteLength - 1
+  var mul = 1
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) >>> 0 & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
+  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+  this[offset] = value
+  return offset + 1
+}
+
+function objectWriteUInt16 (buf, value, offset, littleEndian) {
+  if (value < 0) value = 0xffff + value + 1
+  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
+    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
+      (littleEndian ? i : 1 - i) * 8
+  }
+}
+
+Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = value
+    this[offset + 1] = (value >>> 8)
+  } else {
+    objectWriteUInt16(this, value, offset, true)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 8)
+    this[offset + 1] = value
+  } else {
+    objectWriteUInt16(this, value, offset, false)
+  }
+  return offset + 2
+}
+
+function objectWriteUInt32 (buf, value, offset, littleEndian) {
+  if (value < 0) value = 0xffffffff + value + 1
+  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {
+    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
+  }
+}
+
+Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset + 3] = (value >>> 24)
+    this[offset + 2] = (value >>> 16)
+    this[offset + 1] = (value >>> 8)
+    this[offset] = value
+  } else {
+    objectWriteUInt32(this, value, offset, true)
+  }
+  return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 24)
+    this[offset + 1] = (value >>> 16)
+    this[offset + 2] = (value >>> 8)
+    this[offset + 3] = value
+  } else {
+    objectWriteUInt32(this, value, offset, false)
+  }
+  return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkInt(
+      this, value, offset, byteLength,
+      Math.pow(2, 8 * byteLength - 1) - 1,
+      -Math.pow(2, 8 * byteLength - 1)
+    )
+  }
+
+  var i = 0
+  var mul = 1
+  var sub = value < 0 ? 1 : 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkInt(
+      this, value, offset, byteLength,
+      Math.pow(2, 8 * byteLength - 1) - 1,
+      -Math.pow(2, 8 * byteLength - 1)
+    )
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  var sub = value < 0 ? 1 : 0
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
+  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+  if (value < 0) value = 0xff + value + 1
+  this[offset] = value
+  return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = value
+    this[offset + 1] = (value >>> 8)
+  } else {
+    objectWriteUInt16(this, value, offset, true)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = (value >>> 8)
+    this[offset + 1] = value
+  } else {
+    objectWriteUInt16(this, value, offset, false)
+  }
+  return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    this[offset] = value
…
n6g7 pushed a commit to n6g7/chapter that referenced this pull request Aug 6, 2016
diff --git a/bundle.js b/bundle.js
index 8082ea3..e2686ae 100644
--- a/bundle.js
+++ b/bundle.js
@@ -76,23 +76,23 @@

 	var _Library2 = _interopRequireDefault(_Library);

-	var _NewBook = __webpack_require__(451);
+	var _NewBook = __webpack_require__(442);

 	var _NewBook2 = _interopRequireDefault(_NewBook);

-	var _EditBook = __webpack_require__(457);
+	var _EditBook = __webpack_require__(459);

 	var _EditBook2 = _interopRequireDefault(_EditBook);

-	var _ImportExport = __webpack_require__(459);
+	var _ImportExport = __webpack_require__(461);

 	var _ImportExport2 = _interopRequireDefault(_ImportExport);

-	var _reducer = __webpack_require__(467);
+	var _reducer = __webpack_require__(469);

 	var _reducer2 = _interopRequireDefault(_reducer);

-	__webpack_require__(468);
+	__webpack_require__(470);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -32571,10 +32571,10 @@
 	  value: true
 	});
 	exports.INITIAL_STATE = undefined;
-	exports.setState = setState;
 	exports.addBook = addBook;
 	exports.updateBook = updateBook;
 	exports.removeBook = removeBook;
+	exports.importState = importState;

 	var _immutable = __webpack_require__(254);

@@ -32590,10 +32590,6 @@
 	  }
 	});

-	function setState(state, newState) {
-	  return state.merge(newState);
-	}
-
 	function addBook(state, book) {
 	  if (!book.has('uuid')) {
 	    book = book.set('uuid', _uuid2.default.v4());
@@ -32633,6 +32629,24 @@
 	  });
 	}

+	function importState(state, imported) {
+	  var importedState = (0, _immutable.fromJS)(imported);
+
+	  if (importedState.has('books')) {
+	    importedState = (0, _immutable.Map)({
+	      library: importedState
+	    });
+	  }
+
+	  return importedState.updateIn(['library', 'books'], function (books) {
+	    return books.map(function (book) {
+	      return book.merge({
+	        uuid: _uuid2.default.v4()
+	      });
+	    });
+	  });
+	}
+
 /***/ },
 /* 256 */
 /***/ function(module, exports, __webpack_require__) {
@@ -33071,7 +33085,7 @@

 	module.exports = {
 		"name": "chapter",
-		"version": "0.0.8",
+		"version": "0.0.9",
 		"description": "Digital library for physical books",
 		"main": "index.js",
 		"scripts": {
@@ -33097,6 +33111,7 @@
 			"react-dnd": "^2.1.4",
 			"react-dnd-html5-backend": "^2.1.2",
 			"react-dom": "^15.1.0",
+			"react-input-mask": "^0.7.0",
 			"react-redux": "^4.4.5",
 			"react-router": "^2.4.1",
 			"redux": "^3.5.2",
@@ -33109,6 +33124,7 @@
 			"babel-loader": "^6.2.4",
 			"babel-preset-es2015": "^6.6.0",
 			"babel-preset-react": "^6.5.0",
+			"babel-preset-stage-1": "^6.5.0",
 			"chai": "^3.5.0",
 			"chai-immutable": "astorije/chai-immutable",
 			"css-loader": "^0.23.1",
@@ -33127,6 +33143,7 @@
 		"babel": {
 			"presets": [
 				"es2015",
+				"stage-1",
 				"react"
 			]
 		},
@@ -33145,6 +33162,7 @@
 			},
 			"parserOptions": {
 				"ecmaFeatures": {
+					"experimentalObjectRestSpread": true,
 					"jsx": true
 				},
 				"ecmaVersion": 6,
@@ -33165,17 +33183,10 @@
 	Object.defineProperty(exports, "__esModule", {
 	  value: true
 	});
-	exports.setState = setState;
 	exports.addBook = addBook;
 	exports.updateBook = updateBook;
 	exports.removeBook = removeBook;
-	function setState(state) {
-	  return {
-	    type: 'SET_STATE',
-	    state: state
-	  };
-	}
-
+	exports.importState = importState;
 	function addBook(book) {
 	  return {
 	    type: 'ADD_BOOK',
@@ -33197,6 +33208,13 @@
 	  };
 	}

+	function importState(importedState) {
+	  return {
+	    type: 'IMPORT_STATE',
+	    importedState: importedState
+	  };
+	}
+
 /***/ },
 /* 266 */
 /***/ function(module, exports, __webpack_require__) {
@@ -33265,15 +33283,15 @@

 	var _Button2 = _interopRequireDefault(_Button);

-	var _Header = __webpack_require__(446);
+	var _Header = __webpack_require__(437);

 	var _Header2 = _interopRequireDefault(_Header);

-	var _bookStates = __webpack_require__(448);
+	var _bookStates = __webpack_require__(439);

 	var _bookStates2 = _interopRequireDefault(_bookStates);

-	__webpack_require__(449);
+	__webpack_require__(440);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -40857,7 +40875,7 @@

 	var _BookList2 = _interopRequireDefault(_BookList);

-	var _dragDropTypes = __webpack_require__(445);
+	var _dragDropTypes = __webpack_require__(436);

 	var _dragDropTypes2 = _interopRequireDefault(_dragDropTypes);

@@ -41342,7 +41360,7 @@
 	      this.props.books.map(function (book) {
 	        return _react2.default.createElement(
 	          'li',
-	          { key: book.get('ISBN'), onClick: function onClick() {
+	          { key: book.get('uuid'), onClick: function onClick() {
 	              return _this.goTo(book);
 	            } },
 	          _react2.default.createElement(_DraggableCover2.default, { book: book })
@@ -41378,7 +41396,7 @@

 	var _Cover2 = _interopRequireDefault(_Cover);

-	var _dragDropTypes = __webpack_require__(445);
+	var _dragDropTypes = __webpack_require__(436);

 	var _dragDropTypes2 = _interopRequireDefault(_dragDropTypes);

@@ -41439,17 +41457,11 @@

 	var _immutable = __webpack_require__(254);

-	var _get = __webpack_require__(429);
-
-	var _get2 = _interopRequireDefault(_get);
-
-	var _Loader = __webpack_require__(435);
+	var _Loader = __webpack_require__(429);

 	var _Loader2 = _interopRequireDefault(_Loader);

-	var _apis = __webpack_require__(440);
-
-	__webpack_require__(443);
+	__webpack_require__(434);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -41457,37 +41469,24 @@
 	  displayName: 'Cover',
 	  mixins: [_reactAddonsPureRenderMixin2.default],
 	  propTypes: {
-	    book: _react2.default.PropTypes.instanceOf(_immutable.Map)
-	  },
-	  fetchBookData: function fetchBookData(book) {
-	    var _this = this;
-
-	    (0, _apis.getBookData)(book).then(function (data) {
-	      _this.setState({ loaded: true, data: data });
-
-	      var imageUrl = (0, _get2.default)(_this.state.data, 'imageLinks.thumbnail');
-	      if (imageUrl) {
-	        (0, _apis.getMainColour)(imageUrl).then(function (colour) {
-	          return _this.setState({ colour: colour });
-	        });
-	      }
-	    });
+	    book: _react2.default.PropTypes.instanceOf(_immutable.Map).isRequired,
+	    loading: _react2.default.PropTypes.bool
 	  },
 	  getImage: function getImage() {
-	    if (!this.state || !this.state.loaded) return _react2.default.createElement(_Loader2.default, null);
+	    var _props = this.props;
+	    var book = _props.book;
+	    var loading = _props.loading;

-	    var url = (0, _get2.default)(this.state.data, 'imageLinks.thumbnail', '');
-	    return _react2.default.createElement('img', { src: url, alt: this.props.book.get('title') });
-	  },
-	  componentDidMount: function componentDidMount() {
-	    this.fetchBookData(this.props.book);
-	  },
-	  componentWillReceiveProps: function componentWillReceiveProps(props) {
-	    this.fetchBookData(props.book);
+	    var url = book.getIn(['extra', 'coverUrl']);
+
+	    return loading ? _react2.default.createElement(_Loader2.default, null) : _react2.default.createElement('img', { src: url, alt: book.get('title') });
 	  },
 	  render: function render() {
-	    var book = this.props.book;
-	    var colour = (0, _get2.default)(this.state, 'colour');
+	    var _props2 = this.props;
+	    var book = _props2.book;
+	    var loading = _props2.loading;
+
+	    var colour = !loading ? book.getIn(['extra', 'coverColour']) : null;

 	    return _react2.default.createElement(
 	      'div',
@@ -41518,190 +41517,6 @@
 /* 429 */
 /***/ function(module, exports, __webpack_require__) {

-	var baseGet = __webpack_require__(430);
-
-	/**
-	 * Gets the value at `path` of `object`. If the resolved value is
-	 * `undefined`, the `defaultValue` is used in its place.
-	 *
-	 * @static
-	 * @memberOf _
-	 * @since 3.7.0
-	 * @category Object
-	 * @param {Object} object The object to query.
-	 * @param {Array|string} path The path of the property to get.
-	 * @param {*} [defaultValue] The value returned for `undefined` resolved values.
-	 * @returns {*} Returns the resolved value.
-	 * @example
-	 *
-	 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
-	 *
-	 * _.get(object, 'a[0].b.c');
-	 * // => 3
-	 *
-	 * _.get(object, ['a', '0', 'b', 'c']);
-	 * // => 3
-	 *
-	 * _.get(object, 'a.b.c', 'default');
-	 * // => 'default'
-	 */
-	function get(object, path, defaultValue) {
-	  var result = object == null ? undefined : baseGet(object, path);
-	  return result === undefined ? defaultValue : result;
-	}
-
-	module.exports = get;
-
-
-/***/ },
-/* 430 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var castPath = __webpack_require__(431),
-	    isKey = __webpack_require__(433),
-	    toKey = __webpack_require__(434);
-
-	/**
-	 * The base implementation of `_.get` without support for default values.
-	 *
-	 * @private
-	 * @param {Object} object The object to query.
-	 * @param {Array|string} path The path of the property to get.
-	 * @returns {*} Returns the resolved value.
-	 */
-	function baseGet(object, path) {
-	  path = isKey(path, object) ? [path] : castPath(path);
-
-	  var index = 0,
-	      length = path.length;
-
-	  while (object != null && index < length) {
-	    object = object[toKey(path[index++])];
-	  }
-	  return (index && index == length) ? object : undefined;
-	}
-
-	module.exports = baseGet;
-
-
-/***/ },
-/* 431 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isArray = __webpack_require__(276),
-	    stringToPath = __webpack_require__(432);
-
-	/**
-	 * Casts `value` to a path array if it's not one.
-	 *
-	 * @private
-	 * @param {*} value The value to inspect.
-	 * @returns {Array} Returns the cast property path array.
-	 */
-	function castPath(value) {
-	  return isArray(value) ? value : stringToPath(value);
-	}
-
-	module.exports = castPath;
-
-
-/***/ },
-/* 432 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var memoize = __webpack_require__(408),
-	    toString = __webpack_require__(416);
-
-	/** Used to match property names within property paths. */
-	var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g;
-
-	/** Used to match backslashes in property paths. */
-	var reEscapeChar = /\\(\\)?/g;
-
-	/**
-	 * Converts `string` to a property path array.
-	 *
-	 * @private
-	 * @param {string} string The string to convert.
-	 * @returns {Array} Returns the property path array.
-	 */
-	var stringToPath = memoize(function(string) {
-	  var result = [];
-	  toString(string).replace(rePropName, function(match, number, quote, string) {
-	    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
-	  });
-	  return result;
-	});
-
-	module.exports = stringToPath;
-
-
-/***/ },
-/* 433 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isArray = __webpack_require__(276),
-	    isSymbol = __webpack_require__(335);
-
-	/** Used to match property names within property paths. */
-	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
-	    reIsPlainProp = /^\w*$/;
-
-	/**
-	 * Checks if `value` is a property name and not a property path.
-	 *
-	 * @private
-	 * @param {*} value The value to check.
-	 * @param {Object} [object] The object to query keys on.
-	 * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
-	 */
-	function isKey(value, object) {
-	  if (isArray(value)) {
-	    return false;
-	  }
-	  var type = typeof value;
-	  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
-	      value == null || isSymbol(value)) {
-	    return true;
-	  }
-	  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
-	    (object != null && value in Object(object));
-	}
-
-	module.exports = isKey;
-
-
-/***/ },
-/* 434 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isSymbol = __webpack_require__(335);
-
-	/** Used as references for various `Number` constants. */
-	var INFINITY = 1 / 0;
-
-	/**
-	 * Converts `value` to a string key if it's not a string or symbol.
-	 *
-	 * @private
-	 * @param {*} value The value to inspect.
-	 * @returns {string|symbol} Returns the key.
-	 */
-	function toKey(value) {
-	  if (typeof value == 'string' || isSymbol(value)) {
-	    return value;
-	  }
-	  var result = (value + '');
-	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
-	}
-
-	module.exports = toKey;
-
-
-/***/ },
-/* 435 */
-/***/ function(module, exports, __webpack_require__) {
-
 	'use strict';

 	Object.defineProperty(exports, "__esModule", {
@@ -41716,7 +41531,7 @@

 	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-	__webpack_require__(436);
+	__webpack_require__(430);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -41733,16 +41548,16 @@
 	});

 /***/ },
-/* 436 */
+/* 430 */
 /***/ function(module, exports, __webpack_require__) {

 	// style-loader: Adds some css to the DOM by adding a <style> tag

 	// load the styles
-	var content = __webpack_require__(437);
+	var content = __webpack_require__(431);
 	if(typeof content === 'string') content = [[module.id, content, '']];
 	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
+	var update = __webpack_require__(433)(content, {});
 	if(content.locals) module.exports = content.locals;
 	// Hot Module Replacement
 	if(false) {
@@ -41759,10 +41574,10 @@
 	}

 /***/ },
-/* 437 */
+/* 431 */
 /***/ function(module, exports, __webpack_require__) {

-	exports = module.exports = __webpack_require__(438)();
+	exports = module.exports = __webpack_require__(432)();
 	// imports

@@ -41773,7 +41588,7 @@

 /***/ },
-/* 438 */
+/* 432 */
 /***/ function(module, exports) {

 	/*
@@ -41829,7 +41644,7 @@

 /***/ },
-/* 439 */
+/* 433 */
 /***/ function(module, exports, __webpack_require__) {

 	/*
@@ -42081,189 +41896,895 @@

 /***/ },
-/* 440 */
+/* 434 */
 /***/ function(module, exports, __webpack_require__) {

-	'use strict';
+	// style-loader: Adds some css to the DOM by adding a <style> tag

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-	exports.getBookData = getBookData;
-	exports.getMainColour = getMainColour;
+	// load the styles
+	var content = __webpack_require__(435);
+	if(typeof content === 'string') content = [[module.id, content, '']];
+	// add the styles to the DOM
+	var update = __webpack_require__(433)(content, {});
+	if(content.locals) module.exports = content.locals;
+	// Hot Module Replacement
+	if(false) {
+		// When the styles change, update the <style> tags
+		if(!content.locals) {
+			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl", function() {
+				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl");
+				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
+				update(newContent);
+			});
+		}
+		// When the module is disposed, remove the <style> tags
+		module.hot.dispose(function() { update(); });
+	}

-	var _jquery = __webpack_require__(441);
+/***/ },
+/* 435 */
+/***/ function(module, exports, __webpack_require__) {

-	var _jquery2 = _interopRequireDefault(_jquery);
+	exports = module.exports = __webpack_require__(432)();
+	// imports

-	var _keys = __webpack_require__(442);

-	var _keys2 = _interopRequireDefault(_keys);
+	// module
+	exports.push([module.id, ".cover {\n  background: rgba(0,0,0,0.1);\n  height: 280px;\n  position: relative;\n  width: 180px;\n}\n.cover img {\n  display: block;\n  height: 200px;\n  left: 20px;\n  object-fit: contain;\n  position: absolute;\n  top: 20px;\n  width: 140px;\n}\n.cover .description {\n  background: linear-gradient(to top, #35495d 0%, #35495d 40%, transparent 100%);\n  bottom: 0;\n  display: flex;\n  flex-flow: column nowrap;\n  height: 160px;\n  justify-content: flex-end;\n  padding: 0 10px 10px;\n  position: absolute;\n  width: 100%;\n}\n.cover .description h3 {\n  font-size: 1.6rem;\n  margin-bottom: 5px;\n}\n.cover .description p {\n  font-size: 1rem;\n  margin: 0;\n  opacity: 0.7;\n}\n", ""]);

-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+	// exports

-	var GOOGLE_API = 'https://www.googleapis.com/books/v1/volumes';
-	var TINT_API = '//tint.gnab.fr';

-	function getBookData(book) {
-	  var isbn = book.get('ISBN');
+/***/ },
+/* 436 */
+/***/ function(module, exports) {

-	  // An ISBN is either 10 or 13 chars long
-	  if (!isbn || isbn.length != 10 && isbn.length != 13) return Promise.resolve(null);
+	'use strict';

-	  return new Promise(function (resolve, reject) {
-	    _jquery2.default.ajax({
-	      url: GOOGLE_API,
-	      data: {
-	        q: 'isbn:' + isbn,
-	        key: _keys2.default.google
-	      },
-	      success: function success(data) {
-	        resolve(data.totalItems === 0 ? null : data.items[0].volumeInfo);
-	      },
-	      error: function error(err) {
-	        return reject(err);
-	      }
-	    });
-	  });
-	}
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	var ItemTypes = {
+	  BOOK: 'book'
+	};

-	function getMainColour(imageUrl) {
-	  return new Promise(function (resolve, reject) {
-	    _jquery2.default.ajax({
-	      url: TINT_API + '/' + imageUrl,
-	      success: function success(data) {
-	        return resolve(data.colour);
-	      },
-	      error: reject
-	    });
-	  });
-	}
+	exports.default = ItemTypes;

 /***/ },
-/* 441 */
+/* 437 */
 /***/ function(module, exports, __webpack_require__) {

-	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
-	 * jQuery JavaScript Library v3.0.0
-	 * https://jquery.com/
-	 *
-	 * Includes Sizzle.js
-	 * https://sizzlejs.com/
-	 *
-	 * Copyright jQuery Foundation and other contributors
-	 * Released under the MIT license
-	 * https://jquery.org/license
-	 *
-	 * Date: 2016-06-09T18:02Z
-	 */
-	( function( global, factory ) {
+	'use strict';

-		"use strict";
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});

-		if ( typeof module === "object" && typeof module.exports === "object" ) {
+	var _react = __webpack_require__(1);

-			// For CommonJS and CommonJS-like environments where a proper `window`
-			// is present, execute the factory and get jQuery.
-			// For environments that do not have a `window` with a `document`
-			// (such as Node.js), expose a factory as module.exports.
-			// This accentuates the need for the creation of a real `window`.
-			// e.g. var jQuery = require("jquery")(window);
-			// See ticket #14549 for more info.
-			module.exports = global.document ?
-				factory( global, true ) :
-				function( w ) {
-					if ( !w.document ) {
-						throw new Error( "jQuery requires a window with a document" );
-					}
-					return factory( w );
-				};
-		} else {
-			factory( global );
-		}
+	var _react2 = _interopRequireDefault(_react);

-	// Pass this if window is not defined yet
-	}( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);

-	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
-	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
-	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
-	// enough that all such attempts are guarded in a try block.
-	"use strict";
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-	var arr = [];
+	var _ActionBar = __webpack_require__(438);

-	var document = window.document;
+	var _ActionBar2 = _interopRequireDefault(_ActionBar);

-	var getProto = Object.getPrototypeOf;
+	var _Button = __webpack_require__(425);

-	var slice = arr.slice;
+	var _Button2 = _interopRequireDefault(_Button);

-	var concat = arr.concat;
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

-	var push = arr.push;
+	var DEFAULT_TITLE = 'Chapter';

-	var indexOf = arr.indexOf;
+	exports.default = _react2.default.createClass({
+	  displayName: 'Header',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    title: _react2.default.PropTypes.string,
+	    backButton: _react2.default.PropTypes.bool,
+	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	  },
+	  contextTypes: {
+	    router: _react2.default.PropTypes.object
+	  },
+	  goBack: function goBack() {
+	    this.context.router.goBack();
+	  },
+	  render: function render() {
+	    var title = this.props.title || DEFAULT_TITLE;
+	    var addPreTitle = title !== DEFAULT_TITLE;

-	var class2type = {};
+	    return _react2.default.createElement(
+	      'header',
+	      null,
+	      this.props.backButton && _react2.default.createElement(_Button2.default, { click: this.goBack, label: '<', className: 'back' }),
+	      addPreTitle && _react2.default.createElement(
+	        'p',
+	        null,
+	        DEFAULT_TITLE
+	      ),
+	      _react2.default.createElement(
+	        'h1',
+	        null,
+	        title
+	      ),
+	      _react2.default.createElement(
+	        _ActionBar2.default,
+	        null,
+	        this.props.children
+	      )
+	    );
+	  }
+	});

-	var toString = class2type.toString;
+/***/ },
+/* 438 */
+/***/ function(module, exports, __webpack_require__) {

-	var hasOwn = class2type.hasOwnProperty;
+	'use strict';

-	var fnToString = hasOwn.toString;
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});

-	var ObjectFunctionString = fnToString.call( Object );
+	var _react = __webpack_require__(1);

-	var support = {};
+	var _react2 = _interopRequireDefault(_react);

+	var _reactAddonsPureRenderMixin = __webpack_require__(261);

+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-		function DOMEval( code, doc ) {
-			doc = doc || document;
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

-			var script = doc.createElement( "script" );
+	exports.default = _react2.default.createClass({
+	  displayName: 'ActionBar',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	  },
+	  render: function render() {
+	    return _react2.default.createElement(
+	      'nav',
+	      null,
+	      this.props.children
+	    );
+	  }
+	});

-			script.text = code;
-			doc.head.appendChild( script ).parentNode.removeChild( script );
-		}
+/***/ },
+/* 439 */
+/***/ function(module, exports) {

+	'use strict';

-	var
-		version = "3.0.0",
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.default = {
+	  stock: 'stock',
+	  reading: 'reading',
+	  read: 'read'
+	};

-		// Define a local copy of jQuery
-		jQuery = function( selector, context ) {
+/***/ },
+/* 440 */
+/***/ function(module, exports, __webpack_require__) {

-			// The jQuery object is actually just the init constructor 'enhanced'
-			// Need init if jQuery is called (just allow error to be thrown if not included)
-			return new jQuery.fn.init( selector, context );
-		},
+	// style-loader: Adds some css to the DOM by adding a <style> tag

-		// Support: Android <=4.0 only
-		// Make sure we trim BOM and NBSP
-		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+	// load the styles
+	var content = __webpack_require__(441);
+	if(typeof content === 'string') content = [[module.id, content, '']];
+	// add the styles to the DOM
+	var update = __webpack_require__(433)(content, {});
+	if(content.locals) module.exports = content.locals;
+	// Hot Module Replacement
+	if(false) {
+		// When the styles change, update the <style> tags
+		if(!content.locals) {
+			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl", function() {
+				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl");
+				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
+				update(newContent);
+			});
+		}
+		// When the module is disposed, remove the <style> tags
+		module.hot.dispose(function() { update(); });
+	}

-		// Matches dashed string for camelizing
-		rmsPrefix = /^-ms-/,
-		rdashAlpha = /-([a-z])/g,
+/***/ },
+/* 441 */
+/***/ function(module, exports, __webpack_require__) {

-		// Used by jQuery.camelCase as callback to replace()
-		fcamelCase = function( all, letter ) {
-			return letter.toUpperCase();
-		};
+	exports = module.exports = __webpack_require__(432)();
+	// imports

-	jQuery.fn = jQuery.prototype = {

-		// The current version of jQuery being used
-		jquery: version,
+	// module
+	exports.push([module.id, ".collections {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: space-between;\n}\n.collections section {\n  height: 320px;\n  max-width: 100%;\n  overflow-y: auto;\n}\n.collections section ul {\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: flex-start;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.collections section ul li {\n  margin-right: 20px;\n}\n.collections section ul li:last-child {\n  margin: 0;\n}\n.collections section .announce {\n  color: rgba(255,255,255,0.9);\n  font-size: 1.8rem;\n  left: 50%;\n  margin: 0;\n  position: absolute;\n  top: 50%;\n  transform: translate(-50%, -50%);\n}\n.collections section .announce .button {\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n  height: 30px;\n  line-height: 30px;\n  margin: 0 5px;\n}\n.collections section.stock {\n  background: #ecf0f1;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 100%;\n}\n.collections section.stock h2,\n.collections section.stock::before {\n  background: #bdc3c7;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.stock:hover h2,\n.collections section.stock.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.stock .announce {\n  color: rgba(0,0,0,0.6);\n}\n.collections section.reading {\n  background: #f0c330;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: auto;\n  transition: 0.5s;\n}\n.collections section.reading h2,\n.collections section.reading::before {\n  background: #f19b2c;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.reading:hover h2,\n.collections section.reading.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.reading.hide {\n  min-width: 0;\n  padding: 0;\n}\n.collections section.reading.hover {\n  min-width: 320px;\n}\n.collections section.read {\n  background: #2fd1af;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 50%;\n  flex-grow: 1;\n}\n.collections section.read h2,\n.collections section.read::before {\n  background: #239f85;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.read:hover h2,\n.collections section.read.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n", ""]);

-		constructor: jQuery,
+	// exports

-		// The default length of a jQuery object is 0
-		length: 0,

-		toArray: function() {
+/***/ },
+/* 442 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _reactRedux = __webpack_require__(181);
+
+	var _NewBook = __webpack_require__(443);
+
+	var _NewBook2 = _interopRequireDefault(_NewBook);
+
+	var _actionCreators = __webpack_require__(265);
+
+	var actionCreators = _interopRequireWildcard(_actionCreators);
+
+	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	var mapStateToProps = function mapStateToProps() {
+	  return {};
+	};
+
+	var NewBookContainer = (0, _reactRedux.connect)(mapStateToProps, actionCreators)(_NewBook2.default);
+
+	exports.default = NewBookContainer;
+
+/***/ },
+/* 443 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _react = __webpack_require__(1);
+
+	var _react2 = _interopRequireDefault(_react);
+
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);
+
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
+
+	var _BookForm = __webpack_require__(444);
+
+	var _BookForm2 = _interopRequireDefault(_BookForm);
+
+	var _Button = __webpack_require__(425);
+
+	var _Button2 = _interopRequireDefault(_Button);
+
+	var _Header = __webpack_require__(437);
+
+	var _Header2 = _interopRequireDefault(_Header);
+
+	var _book = __webpack_require__(456);
+
+	__webpack_require__(457);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	exports.default = _react2.default.createClass({
+	  displayName: 'NewBook',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    addBook: _react2.default.PropTypes.func
+	  },
+	  contextTypes: {
+	    router: _react2.default.PropTypes.object
+	  },
+	  update: function update(book) {
+	    this.setState({ book: book });
+	  },
+	  save: function save(book) {
+	    this.props.addBook(book);
+	    this.context.router.push('/');
+	  },
+	  render: function render() {
+	    var _this = this;
+
+	    return _react2.default.createElement(
+	      'div',
+	      null,
+	      _react2.default.createElement(
+	        _Header2.default,
+	        { title: 'Add a book', backButton: true },
+	        _react2.default.createElement(_Button2.default, { click: function click() {
+	            return _this.save(_this.state.book);
+	          }, label: 'Save book' })
+	      ),
+	      _react2.default.createElement(
+	        'section',
+	        { className: 'form' },
+	        _react2.default.createElement(_BookForm2.default, {
+	          book: _book.newBook,
+	          onSubmit: this.save,
+	          onChange: this.update
+	        })
+	      )
+	    );
+	  }
+	});
+
+/***/ },
+/* 444 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _react = __webpack_require__(1);
+
+	var _react2 = _interopRequireDefault(_react);
+
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);
+
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
+
+	var _immutable = __webpack_require__(254);
+
+	var _get = __webpack_require__(445);
+
+	var _get2 = _interopRequireDefault(_get);
+
+	var _Cover = __webpack_require__(428);
+
+	var _Cover2 = _interopRequireDefault(_Cover);
+
+	var _bookStates = __webpack_require__(439);
+
+	var _bookStates2 = _interopRequireDefault(_bookStates);
+
+	var _apis = __webpack_require__(451);
+
+	var _ISBNInput = __webpack_require__(454);
+
+	var _ISBNInput2 = _interopRequireDefault(_ISBNInput);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+	exports.default = _react2.default.createClass({
+	  displayName: 'BookForm',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    onChange: _react2.default.PropTypes.func,
+	    book: _react2.default.PropTypes.instanceOf(_immutable.Map)
+	  },
+	  getInitialState: function getInitialState() {
+	    return {
+	      loading: false
+	    };
+	  },
+	  fetchBookData: function fetchBookData(book) {
+	    var _this = this;
+
+	    this.setState({ loading: true });
+
+	    return (0, _apis.getBookData)(book).then(function (data) {
+	      var coverUrl = (0, _get2.default)(data, 'imageLinks.thumbnail');
+
+	      if (coverUrl) {
+	        return (0, _apis.getMainColour)(coverUrl).then(function (colour) {
+	          _this.setState({ loading: false });
+
+	          return book.merge({
+	            extra: {
+	              coverUrl: coverUrl,
+	              coverColour: colour
+	            }
+	          });
+	        });
+	      } else {
+	        _this.setState({ loading: false });
+	        return book.delete('extra');
+	      }
+	    });
+	  },
+	  handleChange: function handleChange(e) {
+	    var book = this.props.book.merge((0, _immutable.Map)(_defineProperty({}, e.target.id, e.target.value)));
+
+	    if (e.target.id === 'ISBN') {
+	      this.fetchBookData(book).then(this.props.onChange);
+	    } else this.props.onChange(book);
+	  },
+	  render: function render() {
+	    var book = this.props.book;
+	    var loading = this.state.loading;
+
+
+	    return _react2.default.createElement(
+	      'div',
+	      { className: 'bookForm' },
+	      _react2.default.createElement(_Cover2.default, { book: book, loading: loading }),
+	      _react2.default.createElement(
+	        'form',
+	        null,
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'isbn' },
+	            'ISBN'
+	          ),
+	          _react2.default.createElement(_ISBNInput2.default, {
+	            id: 'ISBN',
+	            value: book.get('ISBN'),
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'state' },
+	            'Status'
+	          ),
+	          _react2.default.createElement(
+	            'select',
+	            {
+	              id: 'state',
+	              value: book.get('state'),
+	              onChange: this.handleChange
+	            },
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.stock },
+	              'Stock'
+	            ),
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.reading },
+	              'Reading'
+	            ),
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.read },
+	              'Read'
+	            )
+	          )
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item full' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'title' },
+	            'Title'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'text',
+	            id: 'title',
+	            value: book.get('title'),
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'startDate' },
+	            'Start date'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'date',
+	            id: 'startDate',
+	            value: book.get('startDate'),
+	            placeholder: 'YYYY-MM-DD',
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'endDate' },
+	            'End date'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'date',
+	            id: 'endDate',
+	            value: book.get('endDate'),
+	            placeholder: 'YYYY-MM-DD',
+	            onChange: this.handleChange
+	          })
+	        )
+	      )
+	    );
+	  }
+	});
+
+/***/ },
+/* 445 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var baseGet = __webpack_require__(446);
+
+	/**
+	 * Gets the value at `path` of `object`. If the resolved value is
+	 * `undefined`, the `defaultValue` is used in its place.
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 3.7.0
+	 * @category Object
+	 * @param {Object} object The object to query.
+	 * @param {Array|string} path The path of the property to get.
+	 * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+	 * @returns {*} Returns the resolved value.
+	 * @example
+	 *
+	 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+	 *
+	 * _.get(object, 'a[0].b.c');
+	 * // => 3
+	 *
+	 * _.get(object, ['a', '0', 'b', 'c']);
+	 * // => 3
+	 *
+	 * _.get(object, 'a.b.c', 'default');
+	 * // => 'default'
+	 */
+	function get(object, path, defaultValue) {
+	  var result = object == null ? undefined : baseGet(object, path);
+	  return result === undefined ? defaultValue : result;
+	}
+
+	module.exports = get;
+
+
+/***/ },
+/* 446 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var castPath = __webpack_require__(447),
+	    isKey = __webpack_require__(449),
+	    toKey = __webpack_require__(450);
+
+	/**
+	 * The base implementation of `_.get` without support for default values.
+	 *
+	 * @private
+	 * @param {Object} object The object to query.
+	 * @param {Array|string} path The path of the property to get.
+	 * @returns {*} Returns the resolved value.
+	 */
+	function baseGet(object, path) {
+	  path = isKey(path, object) ? [path] : castPath(path);
+
+	  var index = 0,
+	      length = path.length;
+
+	  while (object != null && index < length) {
+	    object = object[toKey(path[index++])];
+	  }
+	  return (index && index == length) ? object : undefined;
+	}
+
+	module.exports = baseGet;
+
+
+/***/ },
+/* 447 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isArray = __webpack_require__(276),
+	    stringToPath = __webpack_require__(448);
+
+	/**
+	 * Casts `value` to a path array if it's not one.
+	 *
+	 * @private
+	 * @param {*} value The value to inspect.
+	 * @returns {Array} Returns the cast property path array.
+	 */
+	function castPath(value) {
+	  return isArray(value) ? value : stringToPath(value);
+	}
+
+	module.exports = castPath;
+
+
+/***/ },
+/* 448 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var memoize = __webpack_require__(408),
+	    toString = __webpack_require__(416);
+
+	/** Used to match property names within property paths. */
+	var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g;
+
+	/** Used to match backslashes in property paths. */
+	var reEscapeChar = /\\(\\)?/g;
+
+	/**
+	 * Converts `string` to a property path array.
+	 *
+	 * @private
+	 * @param {string} string The string to convert.
+	 * @returns {Array} Returns the property path array.
+	 */
+	var stringToPath = memoize(function(string) {
+	  var result = [];
+	  toString(string).replace(rePropName, function(match, number, quote, string) {
+	    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+	  });
+	  return result;
+	});
+
+	module.exports = stringToPath;
+
+
+/***/ },
+/* 449 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isArray = __webpack_require__(276),
+	    isSymbol = __webpack_require__(335);
+
+	/** Used to match property names within property paths. */
+	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+	    reIsPlainProp = /^\w*$/;
+
+	/**
+	 * Checks if `value` is a property name and not a property path.
+	 *
+	 * @private
+	 * @param {*} value The value to check.
+	 * @param {Object} [object] The object to query keys on.
+	 * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+	 */
+	function isKey(value, object) {
+	  if (isArray(value)) {
+	    return false;
+	  }
+	  var type = typeof value;
+	  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+	      value == null || isSymbol(value)) {
+	    return true;
+	  }
+	  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+	    (object != null && value in Object(object));
+	}
+
+	module.exports = isKey;
+
+
+/***/ },
+/* 450 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isSymbol = __webpack_require__(335);
+
+	/** Used as references for various `Number` constants. */
+	var INFINITY = 1 / 0;
+
+	/**
+	 * Converts `value` to a string key if it's not a string or symbol.
+	 *
+	 * @private
+	 * @param {*} value The value to inspect.
+	 * @returns {string|symbol} Returns the key.
+	 */
+	function toKey(value) {
+	  if (typeof value == 'string' || isSymbol(value)) {
+	    return value;
+	  }
+	  var result = (value + '');
+	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+	}
+
+	module.exports = toKey;
+
+
+/***/ },
+/* 451 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.getBookData = getBookData;
+	exports.getMainColour = getMainColour;
+
+	var _jquery = __webpack_require__(452);
+
+	var _jquery2 = _interopRequireDefault(_jquery);
+
+	var _keys = __webpack_require__(453);
+
+	var _keys2 = _interopRequireDefault(_keys);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	var GOOGLE_API = 'https://www.googleapis.com/books/v1/volumes';
+	var TINT_API = '//tint.gnab.fr';
+
+	function getBookData(book) {
+	  var isbn = book.get('ISBN');
+
+	  // An ISBN is either 10 or 13 chars long
+	  if (!isbn || isbn.length != 10 && isbn.length != 13) return Promise.resolve(null);
+
+	  return new Promise(function (resolve, reject) {
+	    _jquery2.default.ajax({
+	      url: GOOGLE_API,
+	      data: {
+	        q: 'isbn:' + isbn,
+	        key: _keys2.default.google
+	      },
+	      success: function success(data) {
+	        resolve(data.totalItems === 0 ? null : data.items[0].volumeInfo);
+	      },
+	      error: function error(err) {
+	        return reject(err);
+	      }
+	    });
+	  });
+	}
+
+	function getMainColour(imageUrl) {
+	  return new Promise(function (resolve, reject) {
+	    _jquery2.default.ajax({
+	      url: TINT_API + '/' + imageUrl,
+	      success: function success(data) {
+	        return resolve(data.colour);
+	      },
+	      error: reject
+	    });
+	  });
+	}
+
+/***/ },
+/* 452 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
+	 * jQuery JavaScript Library v3.0.0
+	 * https://jquery.com/
+	 *
+	 * Includes Sizzle.js
+	 * https://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * https://jquery.org/license
+	 *
+	 * Date: 2016-06-09T18:02Z
+	 */
+	( function( global, factory ) {
+
+		"use strict";
+
+		if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+			// For CommonJS and CommonJS-like environments where a proper `window`
+			// is present, execute the factory and get jQuery.
+			// For environments that do not have a `window` with a `document`
+			// (such as Node.js), expose a factory as module.exports.
+			// This accentuates the need for the creation of a real `window`.
+			// e.g. var jQuery = require("jquery")(window);
+			// See ticket #14549 for more info.
+			module.exports = global.document ?
+				factory( global, true ) :
+				function( w ) {
+					if ( !w.document ) {
+						throw new Error( "jQuery requires a window with a document" );
+					}
+					return factory( w );
+				};
+		} else {
+			factory( global );
+		}
+
+	// Pass this if window is not defined yet
+	}( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+	// enough that all such attempts are guarded in a try block.
+	"use strict";
+
+	var arr = [];
+
+	var document = window.document;
+
+	var getProto = Object.getPrototypeOf;
+
+	var slice = arr.slice;
+
+	var concat = arr.concat;
+
+	var push = arr.push;
+
+	var indexOf = arr.indexOf;
+
+	var class2type = {};
+
+	var toString = class2type.toString;
+
+	var hasOwn = class2type.hasOwnProperty;
+
+	var fnToString = hasOwn.toString;
+
+	var ObjectFunctionString = fnToString.call( Object );
+
+	var support = {};
+
+
+
+		function DOMEval( code, doc ) {
+			doc = doc || document;
+
+			var script = doc.createElement( "script" );
+
+			script.text = code;
+			doc.head.appendChild( script ).parentNode.removeChild( script );
+		}
+
+
+	var
+		version = "3.0.0",
+
+		// Define a local copy of jQuery
+		jQuery = function( selector, context ) {
+
+			// The jQuery object is actually just the init constructor 'enhanced'
+			// Need init if jQuery is called (just allow error to be thrown if not included)
+			return new jQuery.fn.init( selector, context );
+		},
+
+		// Support: Android <=4.0 only
+		// Make sure we trim BOM and NBSP
+		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+		// Matches dashed string for camelizing
+		rmsPrefix = /^-ms-/,
+		rdashAlpha = /-([a-z])/g,
+
+		// Used by jQuery.camelCase as callback to replace()
+		fcamelCase = function( all, letter ) {
+			return letter.toUpperCase();
+		};
+
+	jQuery.fn = jQuery.prototype = {
+
+		// The current version of jQuery being used
+		jquery: version,
+
+		constructor: jQuery,
+
+		// The default length of a jQuery object is 0
+		length: 0,
+
+		toArray: function() {
 			return slice.call( this );
 		},

@@ -52174,151 +52695,25 @@
 	// Expose jQuery and $ identifiers, even in AMD
 	// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
 	// and CommonJS for browser emulators (#13566)
-	if ( !noGlobal ) {
-		window.jQuery = window.$ = jQuery;
-	}
-
-
-	return jQuery;
-	} ) );
-
-
-/***/ },
-/* 442 */
-/***/ function(module, exports) {
-
-	module.exports = {
-		"google": "AIzaSyCGJLle1uZO6BzHvdYdwY7JEHuHoz8Av-s"
-	};
-
-/***/ },
-/* 443 */
-/***/ function(module, exports, __webpack_require__) {
-
-	// style-loader: Adds some css to the DOM by adding a <style> tag
-
-	// load the styles
-	var content = __webpack_require__(444);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl", function() {
-				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
-
-/***/ },
-/* 444 */
-/***/ function(module, exports, __webpack_require__) {
-
-	exports = module.exports = __webpack_require__(438)();
-	// imports
-
-
-	// module
-	exports.push([module.id, ".cover {\n  background: rgba(0,0,0,0.1);\n  height: 280px;\n  position: relative;\n  width: 180px;\n}\n.cover img {\n  display: block;\n  height: 200px;\n  left: 20px;\n  object-fit: contain;\n  position: absolute;\n  top: 20px;\n  width: 140px;\n}\n.cover .description {\n  background: linear-gradient(to top, #35495d 0%, #35495d 40%, transparent 100%);\n  bottom: 0;\n  display: flex;\n  flex-flow: column nowrap;\n  height: 160px;\n  justify-content: flex-end;\n  padding: 0 10px 10px;\n  position: absolute;\n  width: 100%;\n}\n.cover .description h3 {\n  font-size: 1.6rem;\n  margin-bottom: 5px;\n}\n.cover .description p {\n  font-size: 1rem;\n  margin: 0;\n  opacity: 0.7;\n}\n", ""]);
-
-	// exports
-
-
-/***/ },
-/* 445 */
-/***/ function(module, exports) {
-
-	'use strict';
-
-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-	var ItemTypes = {
-	  BOOK: 'book'
-	};
-
-	exports.default = ItemTypes;
-
-/***/ },
-/* 446 */
-/***/ function(module, exports, __webpack_require__) {
-
-	'use strict';
-
-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-
-	var _react = __webpack_require__(1);
-
-	var _react2 = _interopRequireDefault(_react);
-
-	var _reactAddonsPureRenderMixin = __webpack_require__(261);
-
-	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
-
-	var _ActionBar = __webpack_require__(447);
-
-	var _ActionBar2 = _interopRequireDefault(_ActionBar);
-
-	var _Button = __webpack_require__(425);
-
-	var _Button2 = _interopRequireDefault(_Button);
-
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-	var DEFAULT_TITLE = 'Chapter';
-
-	exports.default = _react2.default.createClass({
-	  displayName: 'Header',
-	  mixins: [_reactAddonsPureRenderMixin2.default],
-	  propTypes: {
-	    title: _react2.default.PropTypes.string,
-	    backButton: _react2.default.PropTypes.bool,
-	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
-	  },
-	  contextTypes: {
-	    router: _react2.default.PropTypes.object
-	  },
-	  goBack: function goBack() {
-	    this.context.router.goBack();
-	  },
-	  render: function render() {
-	    var title = this.props.title || DEFAULT_TITLE;
-	    var addPreTitle = title !== DEFAULT_TITLE;
-
-	    return _react2.default.createElement(
-	      'header',
-	      null,
-	      this.props.backButton && _react2.default.createElement(_Button2.default, { click: this.goBack, label: '<', className: 'back' }),
-	      addPreTitle && _react2.default.createElement(
-	        'p',
-	        null,
-	        DEFAULT_TITLE
-	      ),
-	      _react2.default.createElement(
-	        'h1',
-	        null,
-	        title
-	      ),
-	      _react2.default.createElement(
-	        _ActionBar2.default,
-	        null,
-	        this.props.children
-	      )
-	    );
-	  }
-	});
+	if ( !noGlobal ) {
+		window.jQuery = window.$ = jQuery;
+	}
+
+
+	return jQuery;
+	} ) );
+

 /***/ },
-/* 447 */
+/* 453 */
+/***/ function(module, exports) {
+
+	module.exports = {
+		"google": "changeme"
+	};
+
+/***/ },
+/* 454 */
 /***/ function(module, exports, __webpack_require__) {

 	'use strict';
@@ -52327,6 +52722,8 @@
 	  value: true
 	});

+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
 	var _react = __webpack_require__(1);

 	var _react2 = _interopRequireDefault(_react);
@@ -52335,349 +52732,864 @@

 	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

+	var _reactInputMask = __webpack_require__(455);
+
+	var _reactInputMask2 = _interopRequireDefault(_reactInputMask);
+
 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+	var ISBN_REGEX = /^(9(7[89]?)?)?/;
+	var ISBN_10_MASK = '9 999 99999 9';
+	var ISBN_13_MASK = '999 ' + ISBN_10_MASK;
+
 	exports.default = _react2.default.createClass({
-	  displayName: 'ActionBar',
+	  displayName: 'ISBNInput',
 	  mixins: [_reactAddonsPureRenderMixin2.default],
 	  propTypes: {
-	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	    onChange: _react2.default.PropTypes.func
+	  },
+	  getInitialState: function getInitialState() {
+	    return { mask: ISBN_13_MASK };
+	  },
+	  handleChange: function handleChange(e) {
+	    var value = e.target.value.split(' ').join('');
+	    e.target.value = value;
+
+	    this.setState({
+	      mask: ISBN_REGEX.test(value) ? ISBN_13_MASK : ISBN_10_MASK
+	    });
+
+	    this.props.onChange(e);
 	  },
 	  render: function render() {
-	    return _react2.default.createElement(
-	      'nav',
-	      null,
-	      this.props.children
-	    );
-	  }
-	});
+	    var extraProps = _objectWithoutProperties(this.props, []);

-/***/ },
-/* 448 */
-/***/ function(module, exports) {
+	    var mask = this.state.mask;

-	'use strict';

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
+	    return _react2.default.createElement(_reactInputMask2.default, _extends({
+	      mask: mask,
+	      maskChar: null,
+	      placeholder: '000 0 000 00000 0',
+	      onChange: this.handleChange,
+	      type: 'text'
+	    }, extraProps));
+	  }
 	});
-	exports.default = {
-	  stock: 'stock',
-	  reading: 'reading',
-	  read: 'read'
-	};

 /***/ },
-/* 449 */
+/* 455 */
 /***/ function(module, exports, __webpack_require__) {

-	// style-loader: Adds some css to the DOM by adding a <style> tag
+	// https://github.com/sanniassin/react-input-mask

-	// load the styles
-	var content = __webpack_require__(450);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl", function() {
-				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
+	"use strict";

-/***/ },
-/* 450 */
-/***/ function(module, exports, __webpack_require__) {
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

-	exports = module.exports = __webpack_require__(438)();
-	// imports
+	var React = __webpack_require__(1);

+	var InputElement = React.createClass({
+	    displayName: "InputElement",

-	// module
-	exports.push([module.id, ".collections {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: space-between;\n}\n.collections section {\n  height: 320px;\n  max-width: 100%;\n  overflow-y: auto;\n}\n.collections section ul {\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: flex-start;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.collections section ul li {\n  margin-right: 20px;\n}\n.collections section ul li:last-child {\n  margin: 0;\n}\n.collections section .announce {\n  color: rgba(255,255,255,0.9);\n  font-size: 1.8rem;\n  left: 50%;\n  margin: 0;\n  position: absolute;\n  top: 50%;\n  transform: translate(-50%, -50%);\n}\n.collections section .announce .button {\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n  height: 30px;\n  line-height: 30px;\n  margin: 0 5px;\n}\n.collections section.stock {\n  background: #ecf0f1;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 100%;\n}\n.collections section.stock h2,\n.collections section.stock::before {\n  background: #bdc3c7;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.stock:hover h2,\n.collections section.stock.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.stock .announce {\n  color: rgba(0,0,0,0.6);\n}\n.collections section.reading {\n  background: #f0c330;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: auto;\n  transition: 0.5s;\n}\n.collections section.reading h2,\n.collections section.reading::before {\n  background: #f19b2c;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.reading:hover h2,\n.collections section.reading.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.reading.hide {\n  min-width: 0;\n  padding: 0;\n}\n.collections section.reading.hover {\n  min-width: 320px;\n}\n.collections section.read {\n  background: #2fd1af;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 50%;\n  flex-grow: 1;\n}\n.collections section.read h2,\n.collections section.read::before {\n  background: #239f85;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.read:hover h2,\n.collections section.read.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n", ""]);
+	    defaultCharsRules: {
+	        "9": "[0-9]",
+	        "a": "[A-Za-z]",
+	        "*": "[A-Za-z0-9]"
+	    },
+	    defaultMaskChar: "_",
+	    lastCaretPos: null,
+	    isAndroidBrowser: function () {
+	        var windows = new RegExp("windows", "i");
+	        var firefox = new RegExp("firefox", "i");
+	        var android = new RegExp("android", "i");
+	        var ua = navigator.userAgent;
+	        return !windows.test(ua) && !firefox.test(ua) && android.test(ua);
+	    },
+	    isWindowsPhoneBrowser: function () {
+	        var windows = new RegExp("windows", "i");
+	        var phone = new RegExp("phone", "i");
+	        var ua = navigator.userAgent;
+	        return windows.test(ua) && phone.test(ua);
+	    },
+	    isAndroidFirefox: function () {
+	        var windows = new RegExp("windows", "i");
+	        var firefox = new RegExp("firefox", "i");
+	        var android = new RegExp("android", "i");
+	        var ua = navigator.userAgent;
+	        return !windows.test(ua) && firefox.test(ua) && android.test(ua);
+	    },
+	    isDOMElement: function (element) {
+	        return typeof HTMLElement === "object" ? element instanceof HTMLElement // DOM2
+	        : element.nodeType === 1 && typeof element.nodeName === "string";
+	    },
+	    // getDOMNode is deprecated but we need it to stay compatible with React 0.12
+	    getInputDOMNode: function () {
+	        var input = this.refs.input;

-	// exports
+	        if (!input) {
+	            return null;
+	        }

+	        // React 0.14
+	        if (this.isDOMElement(input)) {
+	            return input;
+	        }

-/***/ },
-/* 451 */
-/***/ function(module, exports, __webpack_require__) {
+	        return input.getDOMNode();
+	    },
+	    enableValueAccessors: function () {
+	        var _this = this;
+
+	        var canUseAccessors = !!(Object.getOwnPropertyDescriptor && Object.getPrototypeOf && Object.defineProperty);
+	        if (canUseAccessors) {
+	            var input = this.getInputDOMNode();
+	            this.valueDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), 'value');
+	            Object.defineProperty(input, 'value', {
+	                configurable: true,
+	                enumerable: true,
+	                get: function () {
+	                    return _this.value;
+	                },
+	                set: function (val) {
+	                    _this.value = val;
+	                    _this.valueDescriptor.set.call(input, val);
+	                }
+	            });
+	        }
+	    },
+	    disableValueAccessors: function () {
+	        var valueDescriptor = this.valueDescriptor;

-	'use strict';
+	        if (!valueDescriptor) {
+	            return;
+	        }
+	        this.valueDescriptor = null;
+	        var input = this.getInputDOMNode();
+	        Object.defineProperty(input, 'value', valueDescriptor);
+	    },
+	    getInputValue: function () {
+	        var input = this.getInputDOMNode();
+	        var valueDescriptor = this.valueDescriptor;

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
+	        var value;
+	        if (valueDescriptor) {
+	            value = valueDescriptor.get.call(input);
+	        } else {
+	            value = input.value;
+	        }

-	var _reactRedux = __webpack_require__(181);
+	        return value;
+	    },
+	    getPrefix: function () {
+	        var prefix = "";
+	        var mask = this.mask;

-	var _NewBook = __webpack_require__(452);
+	        for (var i = 0; i < mask.length && this.isPermanentChar(i); ++i) {
+	            prefix += mask[i];
+	        }
+	        return prefix;
+	    },
+	    getFilledLength: function () {
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	var _NewBook2 = _interopRequireDefault(_NewBook);
+	        var i;
+	        var maskChar = this.maskChar;

-	var _actionCreators = __webpack_require__(265);
+	        if (!maskChar) {
+	            return value.length;
+	        }

-	var actionCreators = _interopRequireWildcard(_actionCreators);
+	        for (i = value.length - 1; i >= 0; --i) {
+	            var character = value[i];
+	            if (!this.isPermanentChar(i) && this.isAllowedChar(character, i)) {
+	                break;
+	            }
+	        }

-	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+	        return ++i || this.getPrefix().length;
+	    },
+	    getLeftEditablePos: function (pos) {
+	        for (var i = pos; i >= 0; --i) {
+	            if (!this.isPermanentChar(i)) {
+	                return i;
+	            }
+	        }
+	        return null;
+	    },
+	    getRightEditablePos: function (pos) {
+	        var mask = this.mask;

-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+	        for (var i = pos; i < mask.length; ++i) {
+	            if (!this.isPermanentChar(i)) {
+	                return i;
+	            }
+	        }
+	        return null;
+	    },
+	    isEmpty: function () {
+	        var _this2 = this;

-	var mapStateToProps = function mapStateToProps() {
-	  return {};
-	};
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	var NewBookContainer = (0, _reactRedux.connect)(mapStateToProps, actionCreators)(_NewBook2.default);
+	        return !value.split("").some(function (character, i) {
+	            return !_this2.isPermanentChar(i) && _this2.isAllowedChar(character, i);
+	        });
+	    },
+	    isFilled: function () {
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	exports.default = NewBookContainer;
+	        return this.getFi…
n6g7 pushed a commit to n6g7/chapter that referenced this pull request Aug 6, 2016
diff --git a/bundle.js b/bundle.js
index 8082ea3..e2686ae 100644
--- a/bundle.js
+++ b/bundle.js
@@ -76,23 +76,23 @@

 	var _Library2 = _interopRequireDefault(_Library);

-	var _NewBook = __webpack_require__(451);
+	var _NewBook = __webpack_require__(442);

 	var _NewBook2 = _interopRequireDefault(_NewBook);

-	var _EditBook = __webpack_require__(457);
+	var _EditBook = __webpack_require__(459);

 	var _EditBook2 = _interopRequireDefault(_EditBook);

-	var _ImportExport = __webpack_require__(459);
+	var _ImportExport = __webpack_require__(461);

 	var _ImportExport2 = _interopRequireDefault(_ImportExport);

-	var _reducer = __webpack_require__(467);
+	var _reducer = __webpack_require__(469);

 	var _reducer2 = _interopRequireDefault(_reducer);

-	__webpack_require__(468);
+	__webpack_require__(470);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -32571,10 +32571,10 @@
 	  value: true
 	});
 	exports.INITIAL_STATE = undefined;
-	exports.setState = setState;
 	exports.addBook = addBook;
 	exports.updateBook = updateBook;
 	exports.removeBook = removeBook;
+	exports.importState = importState;

 	var _immutable = __webpack_require__(254);

@@ -32590,10 +32590,6 @@
 	  }
 	});

-	function setState(state, newState) {
-	  return state.merge(newState);
-	}
-
 	function addBook(state, book) {
 	  if (!book.has('uuid')) {
 	    book = book.set('uuid', _uuid2.default.v4());
@@ -32633,6 +32629,24 @@
 	  });
 	}

+	function importState(state, imported) {
+	  var importedState = (0, _immutable.fromJS)(imported);
+
+	  if (importedState.has('books')) {
+	    importedState = (0, _immutable.Map)({
+	      library: importedState
+	    });
+	  }
+
+	  return importedState.updateIn(['library', 'books'], function (books) {
+	    return books.map(function (book) {
+	      return book.merge({
+	        uuid: _uuid2.default.v4()
+	      });
+	    });
+	  });
+	}
+
 /***/ },
 /* 256 */
 /***/ function(module, exports, __webpack_require__) {
@@ -33071,7 +33085,7 @@

 	module.exports = {
 		"name": "chapter",
-		"version": "0.0.8",
+		"version": "0.0.9",
 		"description": "Digital library for physical books",
 		"main": "index.js",
 		"scripts": {
@@ -33097,6 +33111,7 @@
 			"react-dnd": "^2.1.4",
 			"react-dnd-html5-backend": "^2.1.2",
 			"react-dom": "^15.1.0",
+			"react-input-mask": "^0.7.0",
 			"react-redux": "^4.4.5",
 			"react-router": "^2.4.1",
 			"redux": "^3.5.2",
@@ -33109,6 +33124,7 @@
 			"babel-loader": "^6.2.4",
 			"babel-preset-es2015": "^6.6.0",
 			"babel-preset-react": "^6.5.0",
+			"babel-preset-stage-1": "^6.5.0",
 			"chai": "^3.5.0",
 			"chai-immutable": "astorije/chai-immutable",
 			"css-loader": "^0.23.1",
@@ -33127,6 +33143,7 @@
 		"babel": {
 			"presets": [
 				"es2015",
+				"stage-1",
 				"react"
 			]
 		},
@@ -33145,6 +33162,7 @@
 			},
 			"parserOptions": {
 				"ecmaFeatures": {
+					"experimentalObjectRestSpread": true,
 					"jsx": true
 				},
 				"ecmaVersion": 6,
@@ -33165,17 +33183,10 @@
 	Object.defineProperty(exports, "__esModule", {
 	  value: true
 	});
-	exports.setState = setState;
 	exports.addBook = addBook;
 	exports.updateBook = updateBook;
 	exports.removeBook = removeBook;
-	function setState(state) {
-	  return {
-	    type: 'SET_STATE',
-	    state: state
-	  };
-	}
-
+	exports.importState = importState;
 	function addBook(book) {
 	  return {
 	    type: 'ADD_BOOK',
@@ -33197,6 +33208,13 @@
 	  };
 	}

+	function importState(importedState) {
+	  return {
+	    type: 'IMPORT_STATE',
+	    importedState: importedState
+	  };
+	}
+
 /***/ },
 /* 266 */
 /***/ function(module, exports, __webpack_require__) {
@@ -33265,15 +33283,15 @@

 	var _Button2 = _interopRequireDefault(_Button);

-	var _Header = __webpack_require__(446);
+	var _Header = __webpack_require__(437);

 	var _Header2 = _interopRequireDefault(_Header);

-	var _bookStates = __webpack_require__(448);
+	var _bookStates = __webpack_require__(439);

 	var _bookStates2 = _interopRequireDefault(_bookStates);

-	__webpack_require__(449);
+	__webpack_require__(440);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -40857,7 +40875,7 @@

 	var _BookList2 = _interopRequireDefault(_BookList);

-	var _dragDropTypes = __webpack_require__(445);
+	var _dragDropTypes = __webpack_require__(436);

 	var _dragDropTypes2 = _interopRequireDefault(_dragDropTypes);

@@ -41342,7 +41360,7 @@
 	      this.props.books.map(function (book) {
 	        return _react2.default.createElement(
 	          'li',
-	          { key: book.get('ISBN'), onClick: function onClick() {
+	          { key: book.get('uuid'), onClick: function onClick() {
 	              return _this.goTo(book);
 	            } },
 	          _react2.default.createElement(_DraggableCover2.default, { book: book })
@@ -41378,7 +41396,7 @@

 	var _Cover2 = _interopRequireDefault(_Cover);

-	var _dragDropTypes = __webpack_require__(445);
+	var _dragDropTypes = __webpack_require__(436);

 	var _dragDropTypes2 = _interopRequireDefault(_dragDropTypes);

@@ -41439,17 +41457,11 @@

 	var _immutable = __webpack_require__(254);

-	var _get = __webpack_require__(429);
-
-	var _get2 = _interopRequireDefault(_get);
-
-	var _Loader = __webpack_require__(435);
+	var _Loader = __webpack_require__(429);

 	var _Loader2 = _interopRequireDefault(_Loader);

-	var _apis = __webpack_require__(440);
-
-	__webpack_require__(443);
+	__webpack_require__(434);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -41457,37 +41469,24 @@
 	  displayName: 'Cover',
 	  mixins: [_reactAddonsPureRenderMixin2.default],
 	  propTypes: {
-	    book: _react2.default.PropTypes.instanceOf(_immutable.Map)
-	  },
-	  fetchBookData: function fetchBookData(book) {
-	    var _this = this;
-
-	    (0, _apis.getBookData)(book).then(function (data) {
-	      _this.setState({ loaded: true, data: data });
-
-	      var imageUrl = (0, _get2.default)(_this.state.data, 'imageLinks.thumbnail');
-	      if (imageUrl) {
-	        (0, _apis.getMainColour)(imageUrl).then(function (colour) {
-	          return _this.setState({ colour: colour });
-	        });
-	      }
-	    });
+	    book: _react2.default.PropTypes.instanceOf(_immutable.Map).isRequired,
+	    loading: _react2.default.PropTypes.bool
 	  },
 	  getImage: function getImage() {
-	    if (!this.state || !this.state.loaded) return _react2.default.createElement(_Loader2.default, null);
+	    var _props = this.props;
+	    var book = _props.book;
+	    var loading = _props.loading;

-	    var url = (0, _get2.default)(this.state.data, 'imageLinks.thumbnail', '');
-	    return _react2.default.createElement('img', { src: url, alt: this.props.book.get('title') });
-	  },
-	  componentDidMount: function componentDidMount() {
-	    this.fetchBookData(this.props.book);
-	  },
-	  componentWillReceiveProps: function componentWillReceiveProps(props) {
-	    this.fetchBookData(props.book);
+	    var url = book.getIn(['extra', 'coverUrl']);
+
+	    return loading ? _react2.default.createElement(_Loader2.default, null) : _react2.default.createElement('img', { src: url, alt: book.get('title') });
 	  },
 	  render: function render() {
-	    var book = this.props.book;
-	    var colour = (0, _get2.default)(this.state, 'colour');
+	    var _props2 = this.props;
+	    var book = _props2.book;
+	    var loading = _props2.loading;
+
+	    var colour = !loading ? book.getIn(['extra', 'coverColour']) : null;

 	    return _react2.default.createElement(
 	      'div',
@@ -41518,190 +41517,6 @@
 /* 429 */
 /***/ function(module, exports, __webpack_require__) {

-	var baseGet = __webpack_require__(430);
-
-	/**
-	 * Gets the value at `path` of `object`. If the resolved value is
-	 * `undefined`, the `defaultValue` is used in its place.
-	 *
-	 * @static
-	 * @memberOf _
-	 * @since 3.7.0
-	 * @category Object
-	 * @param {Object} object The object to query.
-	 * @param {Array|string} path The path of the property to get.
-	 * @param {*} [defaultValue] The value returned for `undefined` resolved values.
-	 * @returns {*} Returns the resolved value.
-	 * @example
-	 *
-	 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
-	 *
-	 * _.get(object, 'a[0].b.c');
-	 * // => 3
-	 *
-	 * _.get(object, ['a', '0', 'b', 'c']);
-	 * // => 3
-	 *
-	 * _.get(object, 'a.b.c', 'default');
-	 * // => 'default'
-	 */
-	function get(object, path, defaultValue) {
-	  var result = object == null ? undefined : baseGet(object, path);
-	  return result === undefined ? defaultValue : result;
-	}
-
-	module.exports = get;
-
-
-/***/ },
-/* 430 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var castPath = __webpack_require__(431),
-	    isKey = __webpack_require__(433),
-	    toKey = __webpack_require__(434);
-
-	/**
-	 * The base implementation of `_.get` without support for default values.
-	 *
-	 * @private
-	 * @param {Object} object The object to query.
-	 * @param {Array|string} path The path of the property to get.
-	 * @returns {*} Returns the resolved value.
-	 */
-	function baseGet(object, path) {
-	  path = isKey(path, object) ? [path] : castPath(path);
-
-	  var index = 0,
-	      length = path.length;
-
-	  while (object != null && index < length) {
-	    object = object[toKey(path[index++])];
-	  }
-	  return (index && index == length) ? object : undefined;
-	}
-
-	module.exports = baseGet;
-
-
-/***/ },
-/* 431 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isArray = __webpack_require__(276),
-	    stringToPath = __webpack_require__(432);
-
-	/**
-	 * Casts `value` to a path array if it's not one.
-	 *
-	 * @private
-	 * @param {*} value The value to inspect.
-	 * @returns {Array} Returns the cast property path array.
-	 */
-	function castPath(value) {
-	  return isArray(value) ? value : stringToPath(value);
-	}
-
-	module.exports = castPath;
-
-
-/***/ },
-/* 432 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var memoize = __webpack_require__(408),
-	    toString = __webpack_require__(416);
-
-	/** Used to match property names within property paths. */
-	var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g;
-
-	/** Used to match backslashes in property paths. */
-	var reEscapeChar = /\\(\\)?/g;
-
-	/**
-	 * Converts `string` to a property path array.
-	 *
-	 * @private
-	 * @param {string} string The string to convert.
-	 * @returns {Array} Returns the property path array.
-	 */
-	var stringToPath = memoize(function(string) {
-	  var result = [];
-	  toString(string).replace(rePropName, function(match, number, quote, string) {
-	    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
-	  });
-	  return result;
-	});
-
-	module.exports = stringToPath;
-
-
-/***/ },
-/* 433 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isArray = __webpack_require__(276),
-	    isSymbol = __webpack_require__(335);
-
-	/** Used to match property names within property paths. */
-	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
-	    reIsPlainProp = /^\w*$/;
-
-	/**
-	 * Checks if `value` is a property name and not a property path.
-	 *
-	 * @private
-	 * @param {*} value The value to check.
-	 * @param {Object} [object] The object to query keys on.
-	 * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
-	 */
-	function isKey(value, object) {
-	  if (isArray(value)) {
-	    return false;
-	  }
-	  var type = typeof value;
-	  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
-	      value == null || isSymbol(value)) {
-	    return true;
-	  }
-	  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
-	    (object != null && value in Object(object));
-	}
-
-	module.exports = isKey;
-
-
-/***/ },
-/* 434 */
-/***/ function(module, exports, __webpack_require__) {
-
-	var isSymbol = __webpack_require__(335);
-
-	/** Used as references for various `Number` constants. */
-	var INFINITY = 1 / 0;
-
-	/**
-	 * Converts `value` to a string key if it's not a string or symbol.
-	 *
-	 * @private
-	 * @param {*} value The value to inspect.
-	 * @returns {string|symbol} Returns the key.
-	 */
-	function toKey(value) {
-	  if (typeof value == 'string' || isSymbol(value)) {
-	    return value;
-	  }
-	  var result = (value + '');
-	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
-	}
-
-	module.exports = toKey;
-
-
-/***/ },
-/* 435 */
-/***/ function(module, exports, __webpack_require__) {
-
 	'use strict';

 	Object.defineProperty(exports, "__esModule", {
@@ -41716,7 +41531,7 @@

 	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-	__webpack_require__(436);
+	__webpack_require__(430);

 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -41733,16 +41548,16 @@
 	});

 /***/ },
-/* 436 */
+/* 430 */
 /***/ function(module, exports, __webpack_require__) {

 	// style-loader: Adds some css to the DOM by adding a <style> tag

 	// load the styles
-	var content = __webpack_require__(437);
+	var content = __webpack_require__(431);
 	if(typeof content === 'string') content = [[module.id, content, '']];
 	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
+	var update = __webpack_require__(433)(content, {});
 	if(content.locals) module.exports = content.locals;
 	// Hot Module Replacement
 	if(false) {
@@ -41759,10 +41574,10 @@
 	}

 /***/ },
-/* 437 */
+/* 431 */
 /***/ function(module, exports, __webpack_require__) {

-	exports = module.exports = __webpack_require__(438)();
+	exports = module.exports = __webpack_require__(432)();
 	// imports

@@ -41773,7 +41588,7 @@

 /***/ },
-/* 438 */
+/* 432 */
 /***/ function(module, exports) {

 	/*
@@ -41829,7 +41644,7 @@

 /***/ },
-/* 439 */
+/* 433 */
 /***/ function(module, exports, __webpack_require__) {

 	/*
@@ -42081,189 +41896,895 @@

 /***/ },
-/* 440 */
+/* 434 */
 /***/ function(module, exports, __webpack_require__) {

-	'use strict';
+	// style-loader: Adds some css to the DOM by adding a <style> tag

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-	exports.getBookData = getBookData;
-	exports.getMainColour = getMainColour;
+	// load the styles
+	var content = __webpack_require__(435);
+	if(typeof content === 'string') content = [[module.id, content, '']];
+	// add the styles to the DOM
+	var update = __webpack_require__(433)(content, {});
+	if(content.locals) module.exports = content.locals;
+	// Hot Module Replacement
+	if(false) {
+		// When the styles change, update the <style> tags
+		if(!content.locals) {
+			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl", function() {
+				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl");
+				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
+				update(newContent);
+			});
+		}
+		// When the module is disposed, remove the <style> tags
+		module.hot.dispose(function() { update(); });
+	}

-	var _jquery = __webpack_require__(441);
+/***/ },
+/* 435 */
+/***/ function(module, exports, __webpack_require__) {

-	var _jquery2 = _interopRequireDefault(_jquery);
+	exports = module.exports = __webpack_require__(432)();
+	// imports

-	var _keys = __webpack_require__(442);

-	var _keys2 = _interopRequireDefault(_keys);
+	// module
+	exports.push([module.id, ".cover {\n  background: rgba(0,0,0,0.1);\n  height: 280px;\n  position: relative;\n  width: 180px;\n}\n.cover img {\n  display: block;\n  height: 200px;\n  left: 20px;\n  object-fit: contain;\n  position: absolute;\n  top: 20px;\n  width: 140px;\n}\n.cover .description {\n  background: linear-gradient(to top, #35495d 0%, #35495d 40%, transparent 100%);\n  bottom: 0;\n  display: flex;\n  flex-flow: column nowrap;\n  height: 160px;\n  justify-content: flex-end;\n  padding: 0 10px 10px;\n  position: absolute;\n  width: 100%;\n}\n.cover .description h3 {\n  font-size: 1.6rem;\n  margin-bottom: 5px;\n}\n.cover .description p {\n  font-size: 1rem;\n  margin: 0;\n  opacity: 0.7;\n}\n", ""]);

-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+	// exports

-	var GOOGLE_API = 'https://www.googleapis.com/books/v1/volumes';
-	var TINT_API = '//tint.gnab.fr';

-	function getBookData(book) {
-	  var isbn = book.get('ISBN');
+/***/ },
+/* 436 */
+/***/ function(module, exports) {

-	  // An ISBN is either 10 or 13 chars long
-	  if (!isbn || isbn.length != 10 && isbn.length != 13) return Promise.resolve(null);
+	'use strict';

-	  return new Promise(function (resolve, reject) {
-	    _jquery2.default.ajax({
-	      url: GOOGLE_API,
-	      data: {
-	        q: 'isbn:' + isbn,
-	        key: _keys2.default.google
-	      },
-	      success: function success(data) {
-	        resolve(data.totalItems === 0 ? null : data.items[0].volumeInfo);
-	      },
-	      error: function error(err) {
-	        return reject(err);
-	      }
-	    });
-	  });
-	}
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	var ItemTypes = {
+	  BOOK: 'book'
+	};

-	function getMainColour(imageUrl) {
-	  return new Promise(function (resolve, reject) {
-	    _jquery2.default.ajax({
-	      url: TINT_API + '/' + imageUrl,
-	      success: function success(data) {
-	        return resolve(data.colour);
-	      },
-	      error: reject
-	    });
-	  });
-	}
+	exports.default = ItemTypes;

 /***/ },
-/* 441 */
+/* 437 */
 /***/ function(module, exports, __webpack_require__) {

-	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
-	 * jQuery JavaScript Library v3.0.0
-	 * https://jquery.com/
-	 *
-	 * Includes Sizzle.js
-	 * https://sizzlejs.com/
-	 *
-	 * Copyright jQuery Foundation and other contributors
-	 * Released under the MIT license
-	 * https://jquery.org/license
-	 *
-	 * Date: 2016-06-09T18:02Z
-	 */
-	( function( global, factory ) {
+	'use strict';

-		"use strict";
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});

-		if ( typeof module === "object" && typeof module.exports === "object" ) {
+	var _react = __webpack_require__(1);

-			// For CommonJS and CommonJS-like environments where a proper `window`
-			// is present, execute the factory and get jQuery.
-			// For environments that do not have a `window` with a `document`
-			// (such as Node.js), expose a factory as module.exports.
-			// This accentuates the need for the creation of a real `window`.
-			// e.g. var jQuery = require("jquery")(window);
-			// See ticket #14549 for more info.
-			module.exports = global.document ?
-				factory( global, true ) :
-				function( w ) {
-					if ( !w.document ) {
-						throw new Error( "jQuery requires a window with a document" );
-					}
-					return factory( w );
-				};
-		} else {
-			factory( global );
-		}
+	var _react2 = _interopRequireDefault(_react);

-	// Pass this if window is not defined yet
-	}( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);

-	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
-	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
-	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
-	// enough that all such attempts are guarded in a try block.
-	"use strict";
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-	var arr = [];
+	var _ActionBar = __webpack_require__(438);

-	var document = window.document;
+	var _ActionBar2 = _interopRequireDefault(_ActionBar);

-	var getProto = Object.getPrototypeOf;
+	var _Button = __webpack_require__(425);

-	var slice = arr.slice;
+	var _Button2 = _interopRequireDefault(_Button);

-	var concat = arr.concat;
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

-	var push = arr.push;
+	var DEFAULT_TITLE = 'Chapter';

-	var indexOf = arr.indexOf;
+	exports.default = _react2.default.createClass({
+	  displayName: 'Header',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    title: _react2.default.PropTypes.string,
+	    backButton: _react2.default.PropTypes.bool,
+	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	  },
+	  contextTypes: {
+	    router: _react2.default.PropTypes.object
+	  },
+	  goBack: function goBack() {
+	    this.context.router.goBack();
+	  },
+	  render: function render() {
+	    var title = this.props.title || DEFAULT_TITLE;
+	    var addPreTitle = title !== DEFAULT_TITLE;

-	var class2type = {};
+	    return _react2.default.createElement(
+	      'header',
+	      null,
+	      this.props.backButton && _react2.default.createElement(_Button2.default, { click: this.goBack, label: '<', className: 'back' }),
+	      addPreTitle && _react2.default.createElement(
+	        'p',
+	        null,
+	        DEFAULT_TITLE
+	      ),
+	      _react2.default.createElement(
+	        'h1',
+	        null,
+	        title
+	      ),
+	      _react2.default.createElement(
+	        _ActionBar2.default,
+	        null,
+	        this.props.children
+	      )
+	    );
+	  }
+	});

-	var toString = class2type.toString;
+/***/ },
+/* 438 */
+/***/ function(module, exports, __webpack_require__) {

-	var hasOwn = class2type.hasOwnProperty;
+	'use strict';

-	var fnToString = hasOwn.toString;
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});

-	var ObjectFunctionString = fnToString.call( Object );
+	var _react = __webpack_require__(1);

-	var support = {};
+	var _react2 = _interopRequireDefault(_react);

+	var _reactAddonsPureRenderMixin = __webpack_require__(261);

+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

-		function DOMEval( code, doc ) {
-			doc = doc || document;
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

-			var script = doc.createElement( "script" );
+	exports.default = _react2.default.createClass({
+	  displayName: 'ActionBar',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	  },
+	  render: function render() {
+	    return _react2.default.createElement(
+	      'nav',
+	      null,
+	      this.props.children
+	    );
+	  }
+	});

-			script.text = code;
-			doc.head.appendChild( script ).parentNode.removeChild( script );
-		}
+/***/ },
+/* 439 */
+/***/ function(module, exports) {

+	'use strict';

-	var
-		version = "3.0.0",
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.default = {
+	  stock: 'stock',
+	  reading: 'reading',
+	  read: 'read'
+	};

-		// Define a local copy of jQuery
-		jQuery = function( selector, context ) {
+/***/ },
+/* 440 */
+/***/ function(module, exports, __webpack_require__) {

-			// The jQuery object is actually just the init constructor 'enhanced'
-			// Need init if jQuery is called (just allow error to be thrown if not included)
-			return new jQuery.fn.init( selector, context );
-		},
+	// style-loader: Adds some css to the DOM by adding a <style> tag

-		// Support: Android <=4.0 only
-		// Make sure we trim BOM and NBSP
-		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+	// load the styles
+	var content = __webpack_require__(441);
+	if(typeof content === 'string') content = [[module.id, content, '']];
+	// add the styles to the DOM
+	var update = __webpack_require__(433)(content, {});
+	if(content.locals) module.exports = content.locals;
+	// Hot Module Replacement
+	if(false) {
+		// When the styles change, update the <style> tags
+		if(!content.locals) {
+			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl", function() {
+				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl");
+				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
+				update(newContent);
+			});
+		}
+		// When the module is disposed, remove the <style> tags
+		module.hot.dispose(function() { update(); });
+	}

-		// Matches dashed string for camelizing
-		rmsPrefix = /^-ms-/,
-		rdashAlpha = /-([a-z])/g,
+/***/ },
+/* 441 */
+/***/ function(module, exports, __webpack_require__) {

-		// Used by jQuery.camelCase as callback to replace()
-		fcamelCase = function( all, letter ) {
-			return letter.toUpperCase();
-		};
+	exports = module.exports = __webpack_require__(432)();
+	// imports

-	jQuery.fn = jQuery.prototype = {

-		// The current version of jQuery being used
-		jquery: version,
+	// module
+	exports.push([module.id, ".collections {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: space-between;\n}\n.collections section {\n  height: 320px;\n  max-width: 100%;\n  overflow-y: auto;\n}\n.collections section ul {\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: flex-start;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.collections section ul li {\n  margin-right: 20px;\n}\n.collections section ul li:last-child {\n  margin: 0;\n}\n.collections section .announce {\n  color: rgba(255,255,255,0.9);\n  font-size: 1.8rem;\n  left: 50%;\n  margin: 0;\n  position: absolute;\n  top: 50%;\n  transform: translate(-50%, -50%);\n}\n.collections section .announce .button {\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n  height: 30px;\n  line-height: 30px;\n  margin: 0 5px;\n}\n.collections section.stock {\n  background: #ecf0f1;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 100%;\n}\n.collections section.stock h2,\n.collections section.stock::before {\n  background: #bdc3c7;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.stock:hover h2,\n.collections section.stock.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.stock .announce {\n  color: rgba(0,0,0,0.6);\n}\n.collections section.reading {\n  background: #f0c330;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: auto;\n  transition: 0.5s;\n}\n.collections section.reading h2,\n.collections section.reading::before {\n  background: #f19b2c;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.reading:hover h2,\n.collections section.reading.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.reading.hide {\n  min-width: 0;\n  padding: 0;\n}\n.collections section.reading.hover {\n  min-width: 320px;\n}\n.collections section.read {\n  background: #2fd1af;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 50%;\n  flex-grow: 1;\n}\n.collections section.read h2,\n.collections section.read::before {\n  background: #239f85;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.read:hover h2,\n.collections section.read.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n", ""]);

-		constructor: jQuery,
+	// exports

-		// The default length of a jQuery object is 0
-		length: 0,

-		toArray: function() {
+/***/ },
+/* 442 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _reactRedux = __webpack_require__(181);
+
+	var _NewBook = __webpack_require__(443);
+
+	var _NewBook2 = _interopRequireDefault(_NewBook);
+
+	var _actionCreators = __webpack_require__(265);
+
+	var actionCreators = _interopRequireWildcard(_actionCreators);
+
+	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	var mapStateToProps = function mapStateToProps() {
+	  return {};
+	};
+
+	var NewBookContainer = (0, _reactRedux.connect)(mapStateToProps, actionCreators)(_NewBook2.default);
+
+	exports.default = NewBookContainer;
+
+/***/ },
+/* 443 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _react = __webpack_require__(1);
+
+	var _react2 = _interopRequireDefault(_react);
+
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);
+
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
+
+	var _BookForm = __webpack_require__(444);
+
+	var _BookForm2 = _interopRequireDefault(_BookForm);
+
+	var _Button = __webpack_require__(425);
+
+	var _Button2 = _interopRequireDefault(_Button);
+
+	var _Header = __webpack_require__(437);
+
+	var _Header2 = _interopRequireDefault(_Header);
+
+	var _book = __webpack_require__(456);
+
+	__webpack_require__(457);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	exports.default = _react2.default.createClass({
+	  displayName: 'NewBook',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    addBook: _react2.default.PropTypes.func
+	  },
+	  contextTypes: {
+	    router: _react2.default.PropTypes.object
+	  },
+	  update: function update(book) {
+	    this.setState({ book: book });
+	  },
+	  save: function save(book) {
+	    this.props.addBook(book);
+	    this.context.router.push('/');
+	  },
+	  render: function render() {
+	    var _this = this;
+
+	    return _react2.default.createElement(
+	      'div',
+	      null,
+	      _react2.default.createElement(
+	        _Header2.default,
+	        { title: 'Add a book', backButton: true },
+	        _react2.default.createElement(_Button2.default, { click: function click() {
+	            return _this.save(_this.state.book);
+	          }, label: 'Save book' })
+	      ),
+	      _react2.default.createElement(
+	        'section',
+	        { className: 'form' },
+	        _react2.default.createElement(_BookForm2.default, {
+	          book: _book.newBook,
+	          onSubmit: this.save,
+	          onChange: this.update
+	        })
+	      )
+	    );
+	  }
+	});
+
+/***/ },
+/* 444 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _react = __webpack_require__(1);
+
+	var _react2 = _interopRequireDefault(_react);
+
+	var _reactAddonsPureRenderMixin = __webpack_require__(261);
+
+	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
+
+	var _immutable = __webpack_require__(254);
+
+	var _get = __webpack_require__(445);
+
+	var _get2 = _interopRequireDefault(_get);
+
+	var _Cover = __webpack_require__(428);
+
+	var _Cover2 = _interopRequireDefault(_Cover);
+
+	var _bookStates = __webpack_require__(439);
+
+	var _bookStates2 = _interopRequireDefault(_bookStates);
+
+	var _apis = __webpack_require__(451);
+
+	var _ISBNInput = __webpack_require__(454);
+
+	var _ISBNInput2 = _interopRequireDefault(_ISBNInput);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+	exports.default = _react2.default.createClass({
+	  displayName: 'BookForm',
+	  mixins: [_reactAddonsPureRenderMixin2.default],
+	  propTypes: {
+	    onChange: _react2.default.PropTypes.func,
+	    book: _react2.default.PropTypes.instanceOf(_immutable.Map)
+	  },
+	  getInitialState: function getInitialState() {
+	    return {
+	      loading: false
+	    };
+	  },
+	  fetchBookData: function fetchBookData(book) {
+	    var _this = this;
+
+	    this.setState({ loading: true });
+
+	    return (0, _apis.getBookData)(book).then(function (data) {
+	      var coverUrl = (0, _get2.default)(data, 'imageLinks.thumbnail');
+
+	      if (coverUrl) {
+	        return (0, _apis.getMainColour)(coverUrl).then(function (colour) {
+	          _this.setState({ loading: false });
+
+	          return book.merge({
+	            extra: {
+	              coverUrl: coverUrl,
+	              coverColour: colour
+	            }
+	          });
+	        });
+	      } else {
+	        _this.setState({ loading: false });
+	        return book.delete('extra');
+	      }
+	    });
+	  },
+	  handleChange: function handleChange(e) {
+	    var book = this.props.book.merge((0, _immutable.Map)(_defineProperty({}, e.target.id, e.target.value)));
+
+	    if (e.target.id === 'ISBN') {
+	      this.fetchBookData(book).then(this.props.onChange);
+	    } else this.props.onChange(book);
+	  },
+	  render: function render() {
+	    var book = this.props.book;
+	    var loading = this.state.loading;
+
+
+	    return _react2.default.createElement(
+	      'div',
+	      { className: 'bookForm' },
+	      _react2.default.createElement(_Cover2.default, { book: book, loading: loading }),
+	      _react2.default.createElement(
+	        'form',
+	        null,
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'isbn' },
+	            'ISBN'
+	          ),
+	          _react2.default.createElement(_ISBNInput2.default, {
+	            id: 'ISBN',
+	            value: book.get('ISBN'),
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'state' },
+	            'Status'
+	          ),
+	          _react2.default.createElement(
+	            'select',
+	            {
+	              id: 'state',
+	              value: book.get('state'),
+	              onChange: this.handleChange
+	            },
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.stock },
+	              'Stock'
+	            ),
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.reading },
+	              'Reading'
+	            ),
+	            _react2.default.createElement(
+	              'option',
+	              { value: _bookStates2.default.read },
+	              'Read'
+	            )
+	          )
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item full' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'title' },
+	            'Title'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'text',
+	            id: 'title',
+	            value: book.get('title'),
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'startDate' },
+	            'Start date'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'date',
+	            id: 'startDate',
+	            value: book.get('startDate'),
+	            placeholder: 'YYYY-MM-DD',
+	            onChange: this.handleChange
+	          })
+	        ),
+	        _react2.default.createElement(
+	          'div',
+	          { className: 'item half' },
+	          _react2.default.createElement(
+	            'label',
+	            { htmlFor: 'endDate' },
+	            'End date'
+	          ),
+	          _react2.default.createElement('input', {
+	            type: 'date',
+	            id: 'endDate',
+	            value: book.get('endDate'),
+	            placeholder: 'YYYY-MM-DD',
+	            onChange: this.handleChange
+	          })
+	        )
+	      )
+	    );
+	  }
+	});
+
+/***/ },
+/* 445 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var baseGet = __webpack_require__(446);
+
+	/**
+	 * Gets the value at `path` of `object`. If the resolved value is
+	 * `undefined`, the `defaultValue` is used in its place.
+	 *
+	 * @static
+	 * @memberOf _
+	 * @since 3.7.0
+	 * @category Object
+	 * @param {Object} object The object to query.
+	 * @param {Array|string} path The path of the property to get.
+	 * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+	 * @returns {*} Returns the resolved value.
+	 * @example
+	 *
+	 * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+	 *
+	 * _.get(object, 'a[0].b.c');
+	 * // => 3
+	 *
+	 * _.get(object, ['a', '0', 'b', 'c']);
+	 * // => 3
+	 *
+	 * _.get(object, 'a.b.c', 'default');
+	 * // => 'default'
+	 */
+	function get(object, path, defaultValue) {
+	  var result = object == null ? undefined : baseGet(object, path);
+	  return result === undefined ? defaultValue : result;
+	}
+
+	module.exports = get;
+
+
+/***/ },
+/* 446 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var castPath = __webpack_require__(447),
+	    isKey = __webpack_require__(449),
+	    toKey = __webpack_require__(450);
+
+	/**
+	 * The base implementation of `_.get` without support for default values.
+	 *
+	 * @private
+	 * @param {Object} object The object to query.
+	 * @param {Array|string} path The path of the property to get.
+	 * @returns {*} Returns the resolved value.
+	 */
+	function baseGet(object, path) {
+	  path = isKey(path, object) ? [path] : castPath(path);
+
+	  var index = 0,
+	      length = path.length;
+
+	  while (object != null && index < length) {
+	    object = object[toKey(path[index++])];
+	  }
+	  return (index && index == length) ? object : undefined;
+	}
+
+	module.exports = baseGet;
+
+
+/***/ },
+/* 447 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isArray = __webpack_require__(276),
+	    stringToPath = __webpack_require__(448);
+
+	/**
+	 * Casts `value` to a path array if it's not one.
+	 *
+	 * @private
+	 * @param {*} value The value to inspect.
+	 * @returns {Array} Returns the cast property path array.
+	 */
+	function castPath(value) {
+	  return isArray(value) ? value : stringToPath(value);
+	}
+
+	module.exports = castPath;
+
+
+/***/ },
+/* 448 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var memoize = __webpack_require__(408),
+	    toString = __webpack_require__(416);
+
+	/** Used to match property names within property paths. */
+	var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g;
+
+	/** Used to match backslashes in property paths. */
+	var reEscapeChar = /\\(\\)?/g;
+
+	/**
+	 * Converts `string` to a property path array.
+	 *
+	 * @private
+	 * @param {string} string The string to convert.
+	 * @returns {Array} Returns the property path array.
+	 */
+	var stringToPath = memoize(function(string) {
+	  var result = [];
+	  toString(string).replace(rePropName, function(match, number, quote, string) {
+	    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+	  });
+	  return result;
+	});
+
+	module.exports = stringToPath;
+
+
+/***/ },
+/* 449 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isArray = __webpack_require__(276),
+	    isSymbol = __webpack_require__(335);
+
+	/** Used to match property names within property paths. */
+	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+	    reIsPlainProp = /^\w*$/;
+
+	/**
+	 * Checks if `value` is a property name and not a property path.
+	 *
+	 * @private
+	 * @param {*} value The value to check.
+	 * @param {Object} [object] The object to query keys on.
+	 * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+	 */
+	function isKey(value, object) {
+	  if (isArray(value)) {
+	    return false;
+	  }
+	  var type = typeof value;
+	  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+	      value == null || isSymbol(value)) {
+	    return true;
+	  }
+	  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+	    (object != null && value in Object(object));
+	}
+
+	module.exports = isKey;
+
+
+/***/ },
+/* 450 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var isSymbol = __webpack_require__(335);
+
+	/** Used as references for various `Number` constants. */
+	var INFINITY = 1 / 0;
+
+	/**
+	 * Converts `value` to a string key if it's not a string or symbol.
+	 *
+	 * @private
+	 * @param {*} value The value to inspect.
+	 * @returns {string|symbol} Returns the key.
+	 */
+	function toKey(value) {
+	  if (typeof value == 'string' || isSymbol(value)) {
+	    return value;
+	  }
+	  var result = (value + '');
+	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+	}
+
+	module.exports = toKey;
+
+
+/***/ },
+/* 451 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.getBookData = getBookData;
+	exports.getMainColour = getMainColour;
+
+	var _jquery = __webpack_require__(452);
+
+	var _jquery2 = _interopRequireDefault(_jquery);
+
+	var _keys = __webpack_require__(453);
+
+	var _keys2 = _interopRequireDefault(_keys);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	var GOOGLE_API = 'https://www.googleapis.com/books/v1/volumes';
+	var TINT_API = '//tint.gnab.fr';
+
+	function getBookData(book) {
+	  var isbn = book.get('ISBN');
+
+	  // An ISBN is either 10 or 13 chars long
+	  if (!isbn || isbn.length != 10 && isbn.length != 13) return Promise.resolve(null);
+
+	  return new Promise(function (resolve, reject) {
+	    _jquery2.default.ajax({
+	      url: GOOGLE_API,
+	      data: {
+	        q: 'isbn:' + isbn,
+	        key: _keys2.default.google
+	      },
+	      success: function success(data) {
+	        resolve(data.totalItems === 0 ? null : data.items[0].volumeInfo);
+	      },
+	      error: function error(err) {
+	        return reject(err);
+	      }
+	    });
+	  });
+	}
+
+	function getMainColour(imageUrl) {
+	  return new Promise(function (resolve, reject) {
+	    _jquery2.default.ajax({
+	      url: TINT_API + '/' + imageUrl,
+	      success: function success(data) {
+	        return resolve(data.colour);
+	      },
+	      error: reject
+	    });
+	  });
+	}
+
+/***/ },
+/* 452 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
+	 * jQuery JavaScript Library v3.0.0
+	 * https://jquery.com/
+	 *
+	 * Includes Sizzle.js
+	 * https://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * https://jquery.org/license
+	 *
+	 * Date: 2016-06-09T18:02Z
+	 */
+	( function( global, factory ) {
+
+		"use strict";
+
+		if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+			// For CommonJS and CommonJS-like environments where a proper `window`
+			// is present, execute the factory and get jQuery.
+			// For environments that do not have a `window` with a `document`
+			// (such as Node.js), expose a factory as module.exports.
+			// This accentuates the need for the creation of a real `window`.
+			// e.g. var jQuery = require("jquery")(window);
+			// See ticket #14549 for more info.
+			module.exports = global.document ?
+				factory( global, true ) :
+				function( w ) {
+					if ( !w.document ) {
+						throw new Error( "jQuery requires a window with a document" );
+					}
+					return factory( w );
+				};
+		} else {
+			factory( global );
+		}
+
+	// Pass this if window is not defined yet
+	}( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+	// enough that all such attempts are guarded in a try block.
+	"use strict";
+
+	var arr = [];
+
+	var document = window.document;
+
+	var getProto = Object.getPrototypeOf;
+
+	var slice = arr.slice;
+
+	var concat = arr.concat;
+
+	var push = arr.push;
+
+	var indexOf = arr.indexOf;
+
+	var class2type = {};
+
+	var toString = class2type.toString;
+
+	var hasOwn = class2type.hasOwnProperty;
+
+	var fnToString = hasOwn.toString;
+
+	var ObjectFunctionString = fnToString.call( Object );
+
+	var support = {};
+
+
+
+		function DOMEval( code, doc ) {
+			doc = doc || document;
+
+			var script = doc.createElement( "script" );
+
+			script.text = code;
+			doc.head.appendChild( script ).parentNode.removeChild( script );
+		}
+
+
+	var
+		version = "3.0.0",
+
+		// Define a local copy of jQuery
+		jQuery = function( selector, context ) {
+
+			// The jQuery object is actually just the init constructor 'enhanced'
+			// Need init if jQuery is called (just allow error to be thrown if not included)
+			return new jQuery.fn.init( selector, context );
+		},
+
+		// Support: Android <=4.0 only
+		// Make sure we trim BOM and NBSP
+		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+		// Matches dashed string for camelizing
+		rmsPrefix = /^-ms-/,
+		rdashAlpha = /-([a-z])/g,
+
+		// Used by jQuery.camelCase as callback to replace()
+		fcamelCase = function( all, letter ) {
+			return letter.toUpperCase();
+		};
+
+	jQuery.fn = jQuery.prototype = {
+
+		// The current version of jQuery being used
+		jquery: version,
+
+		constructor: jQuery,
+
+		// The default length of a jQuery object is 0
+		length: 0,
+
+		toArray: function() {
 			return slice.call( this );
 		},

@@ -52174,151 +52695,25 @@
 	// Expose jQuery and $ identifiers, even in AMD
 	// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
 	// and CommonJS for browser emulators (#13566)
-	if ( !noGlobal ) {
-		window.jQuery = window.$ = jQuery;
-	}
-
-
-	return jQuery;
-	} ) );
-
-
-/***/ },
-/* 442 */
-/***/ function(module, exports) {
-
-	module.exports = {
-		"google": "AIzaSyCGJLle1uZO6BzHvdYdwY7JEHuHoz8Av-s"
-	};
-
-/***/ },
-/* 443 */
-/***/ function(module, exports, __webpack_require__) {
-
-	// style-loader: Adds some css to the DOM by adding a <style> tag
-
-	// load the styles
-	var content = __webpack_require__(444);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl", function() {
-				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./cover.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
-
-/***/ },
-/* 444 */
-/***/ function(module, exports, __webpack_require__) {
-
-	exports = module.exports = __webpack_require__(438)();
-	// imports
-
-
-	// module
-	exports.push([module.id, ".cover {\n  background: rgba(0,0,0,0.1);\n  height: 280px;\n  position: relative;\n  width: 180px;\n}\n.cover img {\n  display: block;\n  height: 200px;\n  left: 20px;\n  object-fit: contain;\n  position: absolute;\n  top: 20px;\n  width: 140px;\n}\n.cover .description {\n  background: linear-gradient(to top, #35495d 0%, #35495d 40%, transparent 100%);\n  bottom: 0;\n  display: flex;\n  flex-flow: column nowrap;\n  height: 160px;\n  justify-content: flex-end;\n  padding: 0 10px 10px;\n  position: absolute;\n  width: 100%;\n}\n.cover .description h3 {\n  font-size: 1.6rem;\n  margin-bottom: 5px;\n}\n.cover .description p {\n  font-size: 1rem;\n  margin: 0;\n  opacity: 0.7;\n}\n", ""]);
-
-	// exports
-
-
-/***/ },
-/* 445 */
-/***/ function(module, exports) {
-
-	'use strict';
-
-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-	var ItemTypes = {
-	  BOOK: 'book'
-	};
-
-	exports.default = ItemTypes;
-
-/***/ },
-/* 446 */
-/***/ function(module, exports, __webpack_require__) {
-
-	'use strict';
-
-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
-
-	var _react = __webpack_require__(1);
-
-	var _react2 = _interopRequireDefault(_react);
-
-	var _reactAddonsPureRenderMixin = __webpack_require__(261);
-
-	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);
-
-	var _ActionBar = __webpack_require__(447);
-
-	var _ActionBar2 = _interopRequireDefault(_ActionBar);
-
-	var _Button = __webpack_require__(425);
-
-	var _Button2 = _interopRequireDefault(_Button);
-
-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-	var DEFAULT_TITLE = 'Chapter';
-
-	exports.default = _react2.default.createClass({
-	  displayName: 'Header',
-	  mixins: [_reactAddonsPureRenderMixin2.default],
-	  propTypes: {
-	    title: _react2.default.PropTypes.string,
-	    backButton: _react2.default.PropTypes.bool,
-	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
-	  },
-	  contextTypes: {
-	    router: _react2.default.PropTypes.object
-	  },
-	  goBack: function goBack() {
-	    this.context.router.goBack();
-	  },
-	  render: function render() {
-	    var title = this.props.title || DEFAULT_TITLE;
-	    var addPreTitle = title !== DEFAULT_TITLE;
-
-	    return _react2.default.createElement(
-	      'header',
-	      null,
-	      this.props.backButton && _react2.default.createElement(_Button2.default, { click: this.goBack, label: '<', className: 'back' }),
-	      addPreTitle && _react2.default.createElement(
-	        'p',
-	        null,
-	        DEFAULT_TITLE
-	      ),
-	      _react2.default.createElement(
-	        'h1',
-	        null,
-	        title
-	      ),
-	      _react2.default.createElement(
-	        _ActionBar2.default,
-	        null,
-	        this.props.children
-	      )
-	    );
-	  }
-	});
+	if ( !noGlobal ) {
+		window.jQuery = window.$ = jQuery;
+	}
+
+
+	return jQuery;
+	} ) );
+

 /***/ },
-/* 447 */
+/* 453 */
+/***/ function(module, exports) {
+
+	module.exports = {
+		"google": "changeme"
+	};
+
+/***/ },
+/* 454 */
 /***/ function(module, exports, __webpack_require__) {

 	'use strict';
@@ -52327,6 +52722,8 @@
 	  value: true
 	});

+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
 	var _react = __webpack_require__(1);

 	var _react2 = _interopRequireDefault(_react);
@@ -52335,349 +52732,864 @@

 	var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

+	var _reactInputMask = __webpack_require__(455);
+
+	var _reactInputMask2 = _interopRequireDefault(_reactInputMask);
+
 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+	var ISBN_REGEX = /^(9(7[89]?)?)?/;
+	var ISBN_10_MASK = '9 999 99999 9';
+	var ISBN_13_MASK = '999 ' + ISBN_10_MASK;
+
 	exports.default = _react2.default.createClass({
-	  displayName: 'ActionBar',
+	  displayName: 'ISBNInput',
 	  mixins: [_reactAddonsPureRenderMixin2.default],
 	  propTypes: {
-	    children: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.element, _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.element)])
+	    onChange: _react2.default.PropTypes.func
+	  },
+	  getInitialState: function getInitialState() {
+	    return { mask: ISBN_13_MASK };
+	  },
+	  handleChange: function handleChange(e) {
+	    var value = e.target.value.split(' ').join('');
+	    e.target.value = value;
+
+	    this.setState({
+	      mask: ISBN_REGEX.test(value) ? ISBN_13_MASK : ISBN_10_MASK
+	    });
+
+	    this.props.onChange(e);
 	  },
 	  render: function render() {
-	    return _react2.default.createElement(
-	      'nav',
-	      null,
-	      this.props.children
-	    );
-	  }
-	});
+	    var extraProps = _objectWithoutProperties(this.props, []);

-/***/ },
-/* 448 */
-/***/ function(module, exports) {
+	    var mask = this.state.mask;

-	'use strict';

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
+	    return _react2.default.createElement(_reactInputMask2.default, _extends({
+	      mask: mask,
+	      maskChar: null,
+	      placeholder: '000 0 000 00000 0',
+	      onChange: this.handleChange,
+	      type: 'text'
+	    }, extraProps));
+	  }
 	});
-	exports.default = {
-	  stock: 'stock',
-	  reading: 'reading',
-	  read: 'read'
-	};

 /***/ },
-/* 449 */
+/* 455 */
 /***/ function(module, exports, __webpack_require__) {

-	// style-loader: Adds some css to the DOM by adding a <style> tag
+	// https://github.com/sanniassin/react-input-mask

-	// load the styles
-	var content = __webpack_require__(450);
-	if(typeof content === 'string') content = [[module.id, content, '']];
-	// add the styles to the DOM
-	var update = __webpack_require__(439)(content, {});
-	if(content.locals) module.exports = content.locals;
-	// Hot Module Replacement
-	if(false) {
-		// When the styles change, update the <style> tags
-		if(!content.locals) {
-			module.hot.accept("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl", function() {
-				var newContent = require("!!./../../../node_modules/css-loader/index.js!./../../../node_modules/stylus-loader/index.js!./library.styl");
-				if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
-				update(newContent);
-			});
-		}
-		// When the module is disposed, remove the <style> tags
-		module.hot.dispose(function() { update(); });
-	}
+	"use strict";

-/***/ },
-/* 450 */
-/***/ function(module, exports, __webpack_require__) {
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

-	exports = module.exports = __webpack_require__(438)();
-	// imports
+	var React = __webpack_require__(1);

+	var InputElement = React.createClass({
+	    displayName: "InputElement",

-	// module
-	exports.push([module.id, ".collections {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: space-between;\n}\n.collections section {\n  height: 320px;\n  max-width: 100%;\n  overflow-y: auto;\n}\n.collections section ul {\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: flex-start;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.collections section ul li {\n  margin-right: 20px;\n}\n.collections section ul li:last-child {\n  margin: 0;\n}\n.collections section .announce {\n  color: rgba(255,255,255,0.9);\n  font-size: 1.8rem;\n  left: 50%;\n  margin: 0;\n  position: absolute;\n  top: 50%;\n  transform: translate(-50%, -50%);\n}\n.collections section .announce .button {\n  box-shadow: 0 1px 2px rgba(0,0,0,0.5);\n  height: 30px;\n  line-height: 30px;\n  margin: 0 5px;\n}\n.collections section.stock {\n  background: #ecf0f1;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 100%;\n}\n.collections section.stock h2,\n.collections section.stock::before {\n  background: #bdc3c7;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.stock:hover h2,\n.collections section.stock.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.stock .announce {\n  color: rgba(0,0,0,0.6);\n}\n.collections section.reading {\n  background: #f0c330;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: auto;\n  transition: 0.5s;\n}\n.collections section.reading h2,\n.collections section.reading::before {\n  background: #f19b2c;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.reading:hover h2,\n.collections section.reading.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n.collections section.reading.hide {\n  min-width: 0;\n  padding: 0;\n}\n.collections section.reading.hover {\n  min-width: 320px;\n}\n.collections section.read {\n  background: #2fd1af;\n  padding: 20px 60px 20px 80px;\n  position: relative;\n  flex-basis: 50%;\n  flex-grow: 1;\n}\n.collections section.read h2,\n.collections section.read::before {\n  background: #239f85;\n  bottom: 0;\n  color: transparent;\n  content: '';\n  font-size: 1.8rem;\n  height: 20px;\n  line-height: 20px;\n  position: absolute;\n  right: 100%;\n  text-align: center;\n  top: 0;\n  transition: 0.5s;\n  transform: rotate(-90deg);\n  transform-origin: top right;\n  width: 320px;\n}\n.collections section.read:hover h2,\n.collections section.read.hover h2 {\n  color: rgba(255,255,255,0.8);\n  height: 40px;\n  line-height: 40px;\n}\n", ""]);
+	    defaultCharsRules: {
+	        "9": "[0-9]",
+	        "a": "[A-Za-z]",
+	        "*": "[A-Za-z0-9]"
+	    },
+	    defaultMaskChar: "_",
+	    lastCaretPos: null,
+	    isAndroidBrowser: function () {
+	        var windows = new RegExp("windows", "i");
+	        var firefox = new RegExp("firefox", "i");
+	        var android = new RegExp("android", "i");
+	        var ua = navigator.userAgent;
+	        return !windows.test(ua) && !firefox.test(ua) && android.test(ua);
+	    },
+	    isWindowsPhoneBrowser: function () {
+	        var windows = new RegExp("windows", "i");
+	        var phone = new RegExp("phone", "i");
+	        var ua = navigator.userAgent;
+	        return windows.test(ua) && phone.test(ua);
+	    },
+	    isAndroidFirefox: function () {
+	        var windows = new RegExp("windows", "i");
+	        var firefox = new RegExp("firefox", "i");
+	        var android = new RegExp("android", "i");
+	        var ua = navigator.userAgent;
+	        return !windows.test(ua) && firefox.test(ua) && android.test(ua);
+	    },
+	    isDOMElement: function (element) {
+	        return typeof HTMLElement === "object" ? element instanceof HTMLElement // DOM2
+	        : element.nodeType === 1 && typeof element.nodeName === "string";
+	    },
+	    // getDOMNode is deprecated but we need it to stay compatible with React 0.12
+	    getInputDOMNode: function () {
+	        var input = this.refs.input;

-	// exports
+	        if (!input) {
+	            return null;
+	        }

+	        // React 0.14
+	        if (this.isDOMElement(input)) {
+	            return input;
+	        }

-/***/ },
-/* 451 */
-/***/ function(module, exports, __webpack_require__) {
+	        return input.getDOMNode();
+	    },
+	    enableValueAccessors: function () {
+	        var _this = this;
+
+	        var canUseAccessors = !!(Object.getOwnPropertyDescriptor && Object.getPrototypeOf && Object.defineProperty);
+	        if (canUseAccessors) {
+	            var input = this.getInputDOMNode();
+	            this.valueDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), 'value');
+	            Object.defineProperty(input, 'value', {
+	                configurable: true,
+	                enumerable: true,
+	                get: function () {
+	                    return _this.value;
+	                },
+	                set: function (val) {
+	                    _this.value = val;
+	                    _this.valueDescriptor.set.call(input, val);
+	                }
+	            });
+	        }
+	    },
+	    disableValueAccessors: function () {
+	        var valueDescriptor = this.valueDescriptor;

-	'use strict';
+	        if (!valueDescriptor) {
+	            return;
+	        }
+	        this.valueDescriptor = null;
+	        var input = this.getInputDOMNode();
+	        Object.defineProperty(input, 'value', valueDescriptor);
+	    },
+	    getInputValue: function () {
+	        var input = this.getInputDOMNode();
+	        var valueDescriptor = this.valueDescriptor;

-	Object.defineProperty(exports, "__esModule", {
-	  value: true
-	});
+	        var value;
+	        if (valueDescriptor) {
+	            value = valueDescriptor.get.call(input);
+	        } else {
+	            value = input.value;
+	        }

-	var _reactRedux = __webpack_require__(181);
+	        return value;
+	    },
+	    getPrefix: function () {
+	        var prefix = "";
+	        var mask = this.mask;

-	var _NewBook = __webpack_require__(452);
+	        for (var i = 0; i < mask.length && this.isPermanentChar(i); ++i) {
+	            prefix += mask[i];
+	        }
+	        return prefix;
+	    },
+	    getFilledLength: function () {
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	var _NewBook2 = _interopRequireDefault(_NewBook);
+	        var i;
+	        var maskChar = this.maskChar;

-	var _actionCreators = __webpack_require__(265);
+	        if (!maskChar) {
+	            return value.length;
+	        }

-	var actionCreators = _interopRequireWildcard(_actionCreators);
+	        for (i = value.length - 1; i >= 0; --i) {
+	            var character = value[i];
+	            if (!this.isPermanentChar(i) && this.isAllowedChar(character, i)) {
+	                break;
+	            }
+	        }

-	function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+	        return ++i || this.getPrefix().length;
+	    },
+	    getLeftEditablePos: function (pos) {
+	        for (var i = pos; i >= 0; --i) {
+	            if (!this.isPermanentChar(i)) {
+	                return i;
+	            }
+	        }
+	        return null;
+	    },
+	    getRightEditablePos: function (pos) {
+	        var mask = this.mask;

-	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+	        for (var i = pos; i < mask.length; ++i) {
+	            if (!this.isPermanentChar(i)) {
+	                return i;
+	            }
+	        }
+	        return null;
+	    },
+	    isEmpty: function () {
+	        var _this2 = this;

-	var mapStateToProps = function mapStateToProps() {
-	  return {};
-	};
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	var NewBookContainer = (0, _reactRedux.connect)(mapStateToProps, actionCreators)(_NewBook2.default);
+	        return !value.split("").some(function (character, i) {
+	            return !_this2.isPermanentChar(i) && _this2.isAllowedChar(character, i);
+	        });
+	    },
+	    isFilled: function () {
+	        var value = arguments.length <= 0 || arguments[0] === undefined ? this.state.value : arguments[0];

-	exports.default = NewBookContainer;
+	        return this.getF…
kovzol pushed a commit to geogebra/geogebra that referenced this pull request Aug 8, 2016
@lock lock bot locked as resolved and limited conversation to collaborators May 16, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants