From dd471015ebd824af0caec6bcd9c237a8f10993ac Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Fri, 21 Sep 2018 16:28:37 +0100 Subject: [PATCH 01/17] lein-release plugin: bumped version from 0.2.1 to 0.2.2-SNAPSHOT for next development cycle --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 87b02f2..b085bfa 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject youyesyet "0.2.1" +(defproject youyesyet "0.2.2-SNAPSHOT" :description "Canvassing tool for referenda" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version" From 388bb3fb7d1e820a22474e8e77a7d7e8865150a6 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 23 Sep 2018 13:27:56 +0100 Subject: [PATCH 02/17] Fixed the pin-image regression --- resources/public/error/502.html | 33 ++++++------------- resources/sql/queries.sql | 9 +++++ src/clj/youyesyet/routes/rest.clj | 2 +- src/cljs/youyesyet/canvasser_app/gis.cljs | 12 ++++--- .../youyesyet/canvasser_app/handlers.cljs | 3 +- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/resources/public/error/502.html b/resources/public/error/502.html index d0f149f..2118ede 100644 --- a/resources/public/error/502.html +++ b/resources/public/error/502.html @@ -26,29 +26,16 @@
-
- -
-
-
-
-
-

Error: 502 Bad Gateway

-

- We are suffering an intermittent problem causing an occasional crash of - the Project Hope canvassing application. We apologise for this, and are - working on a permanent fix. -

-

- The application will restart automatically - within five minutes, please take a short break. -

-
-
-
-
-
-
+

Error: 502 Bad Gateway

+

+ We are suffering an intermittent problem causing an occasional crash of + the Project Hope canvassing application. We apologise for this, and are + working on a permanent fix. +

+

+ The application will restart automatically + within five minutes, please take a short break. +


diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 855342f..017463e 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -85,3 +85,12 @@ WHERE roles.id = ln_canvassers_roles.role_id AND ln_canvassers_roles.canvasser_id = :id ORDER BY roles.name, roles.id + +-- :name list-elector-intentions :? :* +-- :doc short form of `list-intentions-by-elector`, returning far less data, +-- for use in `youyesyet.routes.rest/get-local-data`, q.v. +SELECT intentions.id, intentions.option_id, visits.date +FROM intentions, visits +WHERE intentions.visit_id = visits.id +AND intentions.elector_id = :id +ORDER BY visits.date DESC diff --git a/src/clj/youyesyet/routes/rest.clj b/src/clj/youyesyet/routes/rest.clj index cf6d85c..351926e 100644 --- a/src/clj/youyesyet/routes/rest.clj +++ b/src/clj/youyesyet/routes/rest.clj @@ -69,7 +69,7 @@ (fn [e] (assoc e :intentions - (db/list-intentions-by-elector db/*db* {:id (:id e)}))) + (db/list-elector-intentions db/*db* {:id (:id e)}))) (db/list-electors-by-dwelling db/*db* {:id (:id d)})))) (db/list-dwellings-by-address db/*db* {:id (:id a)})))) addresses))) diff --git a/src/cljs/youyesyet/canvasser_app/gis.cljs b/src/cljs/youyesyet/canvasser_app/gis.cljs index b580a3c..3dd3389 100644 --- a/src/cljs/youyesyet/canvasser_app/gis.cljs +++ b/src/cljs/youyesyet/canvasser_app/gis.cljs @@ -2,6 +2,7 @@ :author "Simon Brooke"} youyesyet.canvasser-app.gis (:require [cljs.reader :refer [read-string]] + [clojure.string :refer [lower-case]] [cemerick.url :refer (url url-encode)] [day8.re-frame.http-fx] [re-frame.core :refer [dispatch reg-event-db reg-event-fx subscribe]] @@ -66,13 +67,14 @@ (set (remove nil? - (map - :intention - (mapcat :electors - (:dwellings address)))))] + (map :option_id + (mapcat + :intentions + (mapcat :electors + (:dwellings address))))))] (case (count intentions) 0 "unknown-pin" - 1 (str (name (first intentions)) "-pin") + 1 (lower-case (str (name (first intentions)) "-pin")) "mixed-pin"))) diff --git a/src/cljs/youyesyet/canvasser_app/handlers.cljs b/src/cljs/youyesyet/canvasser_app/handlers.cljs index 0c2ec97..d857bc2 100644 --- a/src/cljs/youyesyet/canvasser_app/handlers.cljs +++ b/src/cljs/youyesyet/canvasser_app/handlers.cljs @@ -181,6 +181,7 @@ ;; TODO: why is this an `-fx`? Does it need to be? (fn [{db :db} [_ response]] + (js/console.log (str ":process-locality: " response)) (js/console.log (str "Updating locality data: " (count response) " addresses " )) (refresh-map-pins) {:db (assoc @@ -194,7 +195,7 @@ (fn [{db :db} [_ response]] ;; TODO: signal something has failed? It doesn't matter very much, unless it keeps failing. - (js/console.log "Failed to fetch locality data") + (js/console.log (str "Failed to fetch locality data" response)) ;; loop to do it again (dispatch [:dispatch-later [{:ms 60000 :dispatch [:fetch-locality]}]]) {:db (assoc From 23591952b870b96aa4ac3d9c20b6b972944ee573 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 23 Sep 2018 14:19:32 +0100 Subject: [PATCH 03/17] Auto-redirect from app to login on 403 --- documentation/authorisation.html | 2 +- documentation/competitors.html | 2 +- documentation/database.html | 2 +- documentation/index.html | 2 +- documentation/scaling.html | 2 +- documentation/userspec.html | 19 +++++++++++++----- documentation/youyesyet.authorisation.html | 2 +- documentation/youyesyet.config.html | 2 +- documentation/youyesyet.db.core.html | 3 ++- documentation/youyesyet.handler.html | 2 +- documentation/youyesyet.layout.html | 2 +- documentation/youyesyet.locality.html | 2 +- documentation/youyesyet.middleware.html | 2 +- documentation/youyesyet.oauth.html | 2 +- documentation/youyesyet.outqueue.html | 2 +- documentation/youyesyet.routes.auto-json.html | 2 +- documentation/youyesyet.routes.auto.html | 2 +- documentation/youyesyet.routes.home.html | 2 +- .../youyesyet.routes.issue-experts.html | 2 +- documentation/youyesyet.routes.manual.html | 2 +- documentation/youyesyet.routes.oauth.html | 2 +- documentation/youyesyet.routes.rest.html | 2 +- documentation/youyesyet.routes.roles.html | 2 +- documentation/youyesyet.routes.services.html | 2 +- documentation/youyesyet.utils.html | 2 +- documentation/youyesyet.validation.html | 2 +- resources/sql/queries.sql | 5 +++-- src/clj/youyesyet/authorisation.clj | 5 +---- .../youyesyet/canvasser_app/handlers.cljs | 20 +++++++++++++++---- 29 files changed, 60 insertions(+), 40 deletions(-) diff --git a/documentation/authorisation.html b/documentation/authorisation.html index 0c83e18..7a7c786 100644 --- a/documentation/authorisation.html +++ b/documentation/authorisation.html @@ -1,6 +1,6 @@ -Security and authorisation

Security and authorisation

+Security and authorisation

Security and authorisation

Essentially we have six levels of authorisation, at essentially increasing levels of sensitivity.

  1. diff --git a/documentation/competitors.html b/documentation/competitors.html index 040c485..22a48d3 100644 --- a/documentation/competitors.html +++ b/documentation/competitors.html @@ -1,6 +1,6 @@ -Competitor Analysis

    Competitor Analysis

    +Competitor Analysis

    Competitor Analysis

    Obviously You Yes Yet? is my baby; I’ve put a lot of thought into it. At the time I started working on it I wasn’t aware of any open source competitors; I did to a web search, and I emailed the Bernie Sanders campaign to see whether their widely admired tools were open source. I didn’t find anything.

    However, I’ve just been pointed to Vote Leave’s Vics tool, and there may well be others.

    There is no room here for ego. What matters is that the Yes campaign gets the best available tool for the job. So it’s important to do competitor analysis, and not to invest too much work into You Yes Yet? unless there’s a realistic possibility of producing a tool which is better than any of the available alternatives. But it’s also the case that by studying competitors we may find ways to improve the design of You Yes Yet?.

    diff --git a/documentation/database.html b/documentation/database.html index 8b07f9f..e347687 100644 --- a/documentation/database.html +++ b/documentation/database.html @@ -1,6 +1,6 @@ -Database Specification

    Database Specification

    +Database Specification

    Database Specification

    Note that this is a work in progress. Read it in concert with the Entity-Relationship Diagram.

    Tables are listed in alphabetical order.

    Address

    diff --git a/documentation/index.html b/documentation/index.html index 662dc9d..30e448f 100644 --- a/documentation/index.html +++ b/documentation/index.html @@ -1,3 +1,3 @@ -Youyesyet 0.2.1-SNAPSHOT

    Youyesyet 0.2.1-SNAPSHOT

    Released under the GNU General Public License,version 2.0 or (at your option) any later version

    Canvassing tool for referenda.

    Installation

    To install, add the following dependency to your project or build file:

    [youyesyet "0.2.1-SNAPSHOT"]

    Topics

    Namespaces

    youyesyet.authorisation

    Field-level authorisation. Messy.

    Public variables and functions:

      youyesyet.config

      Read configuration.

      Public variables and functions:

      youyesyet.db.core

      Database access functions, mostly from Luminus template.

      Public variables and functions:

      youyesyet.handler

      Handlers for starting and stopping the webapp.

      Public variables and functions:

      youyesyet.layout

      Render web pages using Selmer templating markup.

      youyesyet.locality

      FIXME: write docs

      Public variables and functions:

      youyesyet.middleware

      Plumbing, mainly boilerplate from Luminus.

      youyesyet.oauth

      Handle oauth with multiple authenticating authorities.

      youyesyet.outqueue

      Queue of messages waiting to be sent to the server.

      youyesyet.routes.auto

      User interface routes for Youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180920T131125.604Z

      Public variables and functions:

      youyesyet.routes.auto-json

      JSON routes for youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180920T131123.673Z

      Public variables and functions:

      youyesyet.routes.home

      Routes/pages available to unauthenticated users.

      Public variables and functions:

      youyesyet.routes.logged-in

      Routes/pages available to authenticated users.

      Public variables and functions:

      youyesyet.routes.manual

      Manual overrides for auto-generated routes

      Public variables and functions:

        youyesyet.routes.oauth

        OAuth authentication routes - not finished, does not work yet.

        Public variables and functions:

        youyesyet.routes.roles

        Routes/pages available to authenticated users in specific roles.

        youyesyet.routes.services

        REST API.

        Public variables and functions:

        youyesyet.utils

        FIXME: write docs

        Public variables and functions:

        youyesyet.validation

        FIXME: write docs

        Public variables and functions:

          \ No newline at end of file +Youyesyet 0.2.1

          Youyesyet 0.2.1

          Released under the GNU General Public License,version 2.0 or (at your option) any later version

          Canvassing tool for referenda.

          Installation

          To install, add the following dependency to your project or build file:

          [youyesyet "0.2.1"]

          Topics

          Namespaces

          youyesyet.config

          Read configuration.

          Public variables and functions:

          youyesyet.db.core

          Database access functions, mostly from Luminus template.

          Public variables and functions:

          youyesyet.handler

          Handlers for starting and stopping the webapp.

          Public variables and functions:

          youyesyet.layout

          Render web pages using Selmer templating markup.

          youyesyet.locality

          FIXME: write docs

          Public variables and functions:

          youyesyet.middleware

          Plumbing, mainly boilerplate from Luminus.

          youyesyet.oauth

          Handle oauth with multiple authenticating authorities.

          youyesyet.outqueue

          Queue of messages waiting to be sent to the server.

          youyesyet.routes.auto

          User interface routes for Youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180921T143946.559Z

          Public variables and functions:

          youyesyet.routes.auto-json

          JSON routes for youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180921T143945.111Z

          Public variables and functions:

          youyesyet.routes.home

          Routes/pages available to unauthenticated users.

          Public variables and functions:

          youyesyet.routes.logged-in

          Routes/pages available to authenticated users.

          Public variables and functions:

          youyesyet.routes.manual

          Manual overrides for auto-generated routes

          Public variables and functions:

            youyesyet.routes.oauth

            OAuth authentication routes - not finished, does not work yet.

            Public variables and functions:

            youyesyet.routes.roles

            Routes/pages available to authenticated users in specific roles.

            youyesyet.routes.services

            REST API.

            Public variables and functions:

            youyesyet.utils

            FIXME: write docs

            Public variables and functions:

            youyesyet.validation

            FIXME: write docs

            Public variables and functions:

              \ No newline at end of file diff --git a/documentation/scaling.html b/documentation/scaling.html index 4621445..b9dc764 100644 --- a/documentation/scaling.html +++ b/documentation/scaling.html @@ -1,6 +1,6 @@ -YouYesYet: Scaling

              YouYesYet: Scaling

              +YouYesYet: Scaling

              YouYesYet: Scaling

              Suppose the YouYesYet project works and we have thousands or tens of thousands of volunteers across Scotland all out chapping doors at the same time: how do we ensure the system stays up under load?

              Sizing the problem

              There’s no point in building the app if it will break down under load. We need to be persuaded that it is possible to support the maximum predictable load the system might experience.

              diff --git a/documentation/userspec.html b/documentation/userspec.html index e2f9a92..332597a 100644 --- a/documentation/userspec.html +++ b/documentation/userspec.html @@ -1,6 +1,6 @@ -YouYesYet: User-oriented specification

              YouYesYet: User-oriented specification

              +YouYesYet: User-oriented specification

              YouYesYet: User-oriented specification

              Overview

              YouYesYet is a proposed web-app intended to simplify the collection of canvas data from voters, specifically for the upcoming Scottish Independence referendum; it is intended that it should later be adaptable for other canvassing campaigns, but that is a much lower priority.

              General Principles

              @@ -51,15 +51,19 @@

            Map View

            The map view shows a map of the streets immediately around their current location, overlaid, on dwellings where canvas has already been done, with icons indicating the voting preference expressed, and with the dwellings where canvassing is still required marked with an icon indicating this:

            -

            Map View

            +

            Map View

            Selecting a building on the map leads to

              -
            1. On buildings with multiple flats, a schematic view of the flats in the building (I haven’t yet really got a good idea how to do this; it may be just a list). Selecting a flat from this view leads to the Electors View;
            2. +
            3. On buildings with multiple flats, the Building View;
            4. On buildings with only one dwelling, the Electors View.
            +

            Building View

            +

            A list of dwellings in a building.

            +

            Building View

            +

            Selecting a flat from this view leads to the Electors View.

            Electors View

            The Electors View shows a schematic of the registered electors in a dwelling:

            -

            Electors View

            +

            Electors View

            One figure is shown for each elector, labelled with their name. In the dummy pages I’ve shown gendered stick figures, because I believe that in many casesthis will help the canvasser identify the person who has answered the door; but this may be seen as excluding electors with non-binary gender, and, in any case, I believe we don’t actually get gender data (other than salutation) in the electoral roll data. So this may have to be reconsidered.

            Below the figure are:

              @@ -80,8 +84,12 @@ | Other | +------------------------------------------------+ +

              Issues View

              This list will not be hard-coded but will be dynamic; thus, if we find an issue we didn’t predict is regularly coming up on the doorstep an Administrator can add it to the list.

              -

              Selecting the back button from the Issues View returns to the Electors View. Selecting any option from the Issues view leads to a single page giving top level points the canvasser can make to the elector on the doorstep, and a link to a Followup Request form. There is also a ‘back’ button allowing the user to return to the Issues View

              +

              Selecting the back button from the Issues View returns to the Electors View. Selecting any option from the Issues view leads to the Issue View.

              +

              Issue View

              +

              A single page giving top level points the canvasser can make to the elector on the doorstep, regarding the selected issue; and a link to a Followup Request form. There is also a ‘back’ button allowing the user to return to the Issues View.

              +

              Issue View

              Followup Request form

              The Followup Request form is a simple form which allows the canvasser to record a followup request. The elector and the issue are already known from the route taken to reach the form, so don’t have to be filled in by the user. In case of followup by post (we mail them out a leaflet on the issue) the address is also known. If the elector chooses followup by telephone or by email, the canvasser will be prompted for the telephone number or email address respectively.

              +------------------------------------------------+
              @@ -96,6 +104,7 @@
               |                                                |
               +------------------------------------------------+
               
              +

              Followup Request Form

              How Street Canvassers will use the system

              Street Canvassers will typically use the system by

                diff --git a/documentation/youyesyet.authorisation.html b/documentation/youyesyet.authorisation.html index 4b154a7..65b4825 100644 --- a/documentation/youyesyet.authorisation.html +++ b/documentation/youyesyet.authorisation.html @@ -1,4 +1,4 @@ -youyesyet.authorisation documentation

                youyesyet.authorisation

                Field-level authorisation. Messy.
                +youyesyet.authorisation documentation

                youyesyet.authorisation

                Field-level authorisation. Messy.
                 
                \ No newline at end of file diff --git a/documentation/youyesyet.config.html b/documentation/youyesyet.config.html index ecf03f0..d626104 100644 --- a/documentation/youyesyet.config.html +++ b/documentation/youyesyet.config.html @@ -1,4 +1,4 @@ -youyesyet.config documentation

                youyesyet.config

                Read configuration.
                +youyesyet.config documentation

                youyesyet.config

                Read configuration.
                 

                env

                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.db.core.html b/documentation/youyesyet.db.core.html index 50de3d4..fbbe7b3 100644 --- a/documentation/youyesyet.db.core.html +++ b/documentation/youyesyet.db.core.html @@ -1,6 +1,6 @@ -youyesyet.db.core documentation

                youyesyet.db.core

                Database access functions, mostly from Luminus template.
                +youyesyet.db.core documentation

                youyesyet.db.core

                Database access functions, mostly from Luminus template.
                 

                *db*

                dynamic

                FIXME: write docs

                create-address!

                creates a new address record
                 

                create-address!-sqlvec

                creates a new address record (sqlvec)
                 

                create-authority!

                creates a new authority record
                @@ -134,6 +134,7 @@
                 

                list-dwellings-by-address

                lists all existing dwelling records related to a given address
                 

                list-dwellings-by-address-sqlvec

                lists all existing dwelling records related to a given address (sqlvec)
                 

                list-dwellings-sqlvec

                lists all existing dwelling records (sqlvec)
                +

                list-elector-intentions

                short form of `list-intentions-by-elector`, returning far less data, for use in `youyesyet.routes.rest/get-local-data`, q.v.
                 

                list-electors

                lists all existing elector records
                 

                list-electors-by-dwelling

                lists all existing dwelling records related to a given elector
                 

                list-electors-by-dwelling-sqlvec

                lists all existing dwelling records related to a given elector (sqlvec)
                diff --git a/documentation/youyesyet.handler.html b/documentation/youyesyet.handler.html
                index ba0b8df..b38a338 100644
                --- a/documentation/youyesyet.handler.html
                +++ b/documentation/youyesyet.handler.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.handler documentation

                youyesyet.handler

                Handlers for starting and stopping the webapp.
                +youyesyet.handler documentation

                youyesyet.handler

                Handlers for starting and stopping the webapp.
                 

                app

                FIXME: write docs

                app-routes

                FIXME: write docs

                destroy

                (destroy)
                destroy will be called when your application
                 shuts down, put any clean up code here

                init

                (init)
                init will be called once when
                 app is deployed as a servlet on
                diff --git a/documentation/youyesyet.layout.html b/documentation/youyesyet.layout.html
                index 46cdc96..0be9065 100644
                --- a/documentation/youyesyet.layout.html
                +++ b/documentation/youyesyet.layout.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.layout documentation

                youyesyet.layout

                Render web pages using Selmer templating markup.
                +youyesyet.layout documentation

                youyesyet.layout

                Render web pages using Selmer templating markup.
                 

                *app-context*

                dynamic

                FIXME: write docs

                *user*

                dynamic

                FIXME: write docs

                error-page

                (error-page error-details)
                error-details should be a map containing the following keys:
                 :status - error status
                 :title - error title (optional)
                diff --git a/documentation/youyesyet.locality.html b/documentation/youyesyet.locality.html
                index 4791272..dd328e7 100644
                --- a/documentation/youyesyet.locality.html
                +++ b/documentation/youyesyet.locality.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.locality documentation

                youyesyet.locality

                FIXME: write docs

                locality

                (locality latitude longitude)
                Compute the locality index for this `latitude`/`longitude` pair.
                +youyesyet.locality documentation

                youyesyet.locality

                FIXME: write docs

                locality

                (locality latitude longitude)
                Compute the locality index for this `latitude`/`longitude` pair.
                 

                neighbouring-localities

                (neighbouring-localities locality)
                Return this locality with the localities immediately
                 north east, north, north west, east, west, south west,
                 south and south east of it.
                \ No newline at end of file diff --git a/documentation/youyesyet.middleware.html b/documentation/youyesyet.middleware.html index b34fe75..de9ddb3 100644 --- a/documentation/youyesyet.middleware.html +++ b/documentation/youyesyet.middleware.html @@ -1,5 +1,5 @@ -youyesyet.middleware documentation

                youyesyet.middleware

                Plumbing, mainly boilerplate from Luminus.
                +youyesyet.middleware documentation

                youyesyet.middleware

                Plumbing, mainly boilerplate from Luminus.
                 

                wrap-base

                (wrap-base handler)
                FIXME: write docs

                wrap-context

                (wrap-context handler)
                FIXME: write docs

                wrap-csrf

                (wrap-csrf handler)
                FIXME: write docs

                wrap-formats

                (wrap-formats handler)
                FIXME: write docs

                wrap-internal-error

                (wrap-internal-error handler)
                FIXME: write docs

                wrap-user

                (wrap-user handler)
                Dynamically bind *user* to the user in the session, if any, so that it
                 is available in layout/render, q.v.
                \ No newline at end of file diff --git a/documentation/youyesyet.oauth.html b/documentation/youyesyet.oauth.html index 586c8bc..ab9e15f 100644 --- a/documentation/youyesyet.oauth.html +++ b/documentation/youyesyet.oauth.html @@ -1,6 +1,6 @@ -youyesyet.oauth documentation

                youyesyet.oauth

                Handle oauth with multiple authenticating authorities.
                +youyesyet.oauth documentation

                youyesyet.oauth

                Handle oauth with multiple authenticating authorities.
                 

                auth-redirect-uri

                (auth-redirect-uri request-token authority)
                Gets the URI the user should be redirected to when authenticating.
                 

                authority!

                FIXME: write docs

                fetch-access-token

                (fetch-access-token request_token authority)
                FIXME: write docs

                fetch-request-token

                (fetch-request-token request auth)(fetch-request-token request)
                Fetches a request token from the authority implied by this `request`.
                 

                get-authorities

                (get-authorities _)
                Fetch the authorities from the database and return a map of them.
                diff --git a/documentation/youyesyet.outqueue.html b/documentation/youyesyet.outqueue.html
                index d5f18fb..e3da898 100644
                --- a/documentation/youyesyet.outqueue.html
                +++ b/documentation/youyesyet.outqueue.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.outqueue documentation

                youyesyet.outqueue

                Queue of messages waiting to be sent to the server.
                +youyesyet.outqueue documentation

                youyesyet.outqueue

                Queue of messages waiting to be sent to the server.
                 

                add!

                (add! q item)
                Add this item to the queue.
                 

                count

                (count q)
                Return the count of items currently in the queue.
                 

                lock!

                (lock! q)
                FIXME: write docs

                locked?

                (locked? q)
                FIXME: write docs

                maybe-process-next

                (maybe-process-next q process)
                Apply this process, assumed to be a function of one argument, to the next
                diff --git a/documentation/youyesyet.routes.auto-json.html b/documentation/youyesyet.routes.auto-json.html
                index 96f050a..a0f6943 100644
                --- a/documentation/youyesyet.routes.auto-json.html
                +++ b/documentation/youyesyet.routes.auto-json.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.routes.auto-json documentation

                youyesyet.routes.auto-json

                JSON routes for youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180920T131123.673Z
                +youyesyet.routes.auto-json documentation

                youyesyet.routes.auto-json

                JSON routes for youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180921T143945.111Z
                 

                auto-rest-routes

                FIXME: write docs

                create-address!

                (create-address! request)
                Auto-generated method to insert one record to the `addresses` table. Expects the following key(s) to be present in `params`: `(:address :postcode :district_id :latitude :longitude)`. Returns a map containing the keys `#{"id"}` identifying the record created.
                 

                create-authority!

                (create-authority! request)
                Auto-generated method to insert one record to the `authorities` table. Expects the following key(s) to be present in `params`: `(:request-token-uri :access-token-uri :authorize-uri :consumer-key :consumer-secret :id)`. Returns a map containing the keys `#{"id"}` identifying the record created.
                 

                create-canvasser!

                (create-canvasser! request)
                Auto-generated method to insert one record to the `canvassers` table. Expects the following key(s) to be present in `params`: `(:username :fullname :avatar :bio :elector_id :address_id :phone :email :authority_id :authorised)`. Returns a map containing the keys `#{"id"}` identifying the record created.
                diff --git a/documentation/youyesyet.routes.auto.html b/documentation/youyesyet.routes.auto.html
                index 647307f..5ab54ce 100644
                --- a/documentation/youyesyet.routes.auto.html
                +++ b/documentation/youyesyet.routes.auto.html
                @@ -1,5 +1,5 @@
                 
                -youyesyet.routes.auto documentation

                youyesyet.routes.auto

                User interface routes for Youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180920T131125.604Z
                +youyesyet.routes.auto documentation

                youyesyet.routes.auto

                User interface routes for Youyesyet auto-generated by [Application Description Language framework](https://github.com/simon-brooke/adl) at 20180921T143946.559Z
                 

                auto-selmer-routes

                FIXME: write docs

                get-form-addresses-Address

                (get-form-addresses-Address request)
                FIXME: write docs

                get-form-authorities-Authority

                (get-form-authorities-Authority request)
                FIXME: write docs

                get-form-canvassers-Canvasser

                (get-form-canvassers-Canvasser request)
                FIXME: write docs

                get-form-districts-District

                (get-form-districts-District request)
                FIXME: write docs

                get-form-dwellings-Dwelling

                (get-form-dwellings-Dwelling request)
                FIXME: write docs

                get-form-electors-Elector

                (get-form-electors-Elector request)
                FIXME: write docs

                get-form-events-Events

                (get-form-events-Events request)
                FIXME: write docs

                get-form-followupactions-Followupaction

                (get-form-followupactions-Followupaction request)
                FIXME: write docs

                get-form-followupmethods-Followupmethod

                (get-form-followupmethods-Followupmethod request)
                FIXME: write docs

                get-form-followuprequests-Followuprequest

                (get-form-followuprequests-Followuprequest request)
                FIXME: write docs

                get-form-genders-Gender

                (get-form-genders-Gender request)
                FIXME: write docs

                get-form-intentions-Intention

                (get-form-intentions-Intention request)
                FIXME: write docs

                get-form-issues-Issue

                (get-form-issues-Issue request)
                FIXME: write docs

                get-form-options-Option

                (get-form-options-Option request)
                FIXME: write docs

                get-form-roles-Role

                (get-form-roles-Role request)
                FIXME: write docs

                get-form-teams-Team

                (get-form-teams-Team request)
                FIXME: write docs

                get-form-visits-Visit

                (get-form-visits-Visit request)
                FIXME: write docs

                get-list-addresses-Addresses

                (get-list-addresses-Addresses request)
                FIXME: write docs

                get-list-authorities-Authorities

                (get-list-authorities-Authorities request)
                FIXME: write docs

                get-list-canvassers-Canvassers

                (get-list-canvassers-Canvassers request)
                FIXME: write docs

                get-list-districts-Districts

                (get-list-districts-Districts request)
                FIXME: write docs

                get-list-dwellings-Dwellings

                (get-list-dwellings-Dwellings request)
                FIXME: write docs

                get-list-electors-Electors

                (get-list-electors-Electors request)
                FIXME: write docs

                get-list-events-Events

                (get-list-events-Events request)
                FIXME: write docs

                get-list-followupactions-Followupactions

                (get-list-followupactions-Followupactions request)
                FIXME: write docs

                get-list-followupmethods-Followupmethods

                (get-list-followupmethods-Followupmethods request)
                FIXME: write docs

                get-list-followuprequests-Followuprequests

                (get-list-followuprequests-Followuprequests request)
                FIXME: write docs

                get-list-genders-Genders

                (get-list-genders-Genders request)
                FIXME: write docs

                get-list-intentions-Intentions

                (get-list-intentions-Intentions request)
                FIXME: write docs

                get-list-issues-Issues

                (get-list-issues-Issues request)
                FIXME: write docs

                get-list-options-Options

                (get-list-options-Options request)
                FIXME: write docs

                get-list-roles-Roles

                (get-list-roles-Roles request)
                FIXME: write docs

                get-list-teams-Teams

                (get-list-teams-Teams request)
                FIXME: write docs

                get-list-visits-Visits

                (get-list-visits-Visits request)
                FIXME: write docs

                index

                (index r)
                FIXME: write docs

                post-form-addresses-Address

                (post-form-addresses-Address request)
                FIXME: write docs

                post-form-authorities-Authority

                (post-form-authorities-Authority request)
                FIXME: write docs

                post-form-canvassers-Canvasser

                (post-form-canvassers-Canvasser request)
                FIXME: write docs

                post-form-districts-District

                (post-form-districts-District request)
                FIXME: write docs

                post-form-dwellings-Dwelling

                (post-form-dwellings-Dwelling request)
                FIXME: write docs

                post-form-electors-Elector

                (post-form-electors-Elector request)
                FIXME: write docs

                post-form-events-Events

                (post-form-events-Events request)
                FIXME: write docs

                post-form-followupactions-Followupaction

                (post-form-followupactions-Followupaction request)
                FIXME: write docs

                post-form-followupmethods-Followupmethod

                (post-form-followupmethods-Followupmethod request)
                FIXME: write docs

                post-form-followuprequests-Followuprequest

                (post-form-followuprequests-Followuprequest request)
                FIXME: write docs

                post-form-genders-Gender

                (post-form-genders-Gender request)
                FIXME: write docs

                post-form-intentions-Intention

                (post-form-intentions-Intention request)
                FIXME: write docs

                post-form-issues-Issue

                (post-form-issues-Issue request)
                FIXME: write docs

                post-form-options-Option

                (post-form-options-Option request)
                FIXME: write docs

                post-form-roles-Role

                (post-form-roles-Role request)
                FIXME: write docs

                post-form-teams-Team

                (post-form-teams-Team request)
                FIXME: write docs

                post-form-visits-Visit

                (post-form-visits-Visit request)
                FIXME: write docs

                post-list-addresses-Addresses

                (post-list-addresses-Addresses request)
                FIXME: write docs

                post-list-authorities-Authorities

                (post-list-authorities-Authorities request)
                FIXME: write docs

                post-list-canvassers-Canvassers

                (post-list-canvassers-Canvassers request)
                FIXME: write docs

                post-list-districts-Districts

                (post-list-districts-Districts request)
                FIXME: write docs

                post-list-dwellings-Dwellings

                (post-list-dwellings-Dwellings request)
                FIXME: write docs

                post-list-electors-Electors

                (post-list-electors-Electors request)
                FIXME: write docs

                post-list-events-Events

                (post-list-events-Events request)
                FIXME: write docs

                post-list-followupactions-Followupactions

                (post-list-followupactions-Followupactions request)
                FIXME: write docs

                post-list-followupmethods-Followupmethods

                (post-list-followupmethods-Followupmethods request)
                FIXME: write docs

                post-list-followuprequests-Followuprequests

                (post-list-followuprequests-Followuprequests request)
                FIXME: write docs

                post-list-genders-Genders

                (post-list-genders-Genders request)
                FIXME: write docs

                post-list-intentions-Intentions

                (post-list-intentions-Intentions request)
                FIXME: write docs

                post-list-issues-Issues

                (post-list-issues-Issues request)
                FIXME: write docs

                post-list-options-Options

                (post-list-options-Options request)
                FIXME: write docs

                post-list-roles-Roles

                (post-list-roles-Roles request)
                FIXME: write docs

                post-list-teams-Teams

                (post-list-teams-Teams request)
                FIXME: write docs

                post-list-visits-Visits

                (post-list-visits-Visits request)
                FIXME: write docs

                raw-resolve-handler

                (raw-resolve-handler n)
                Prefer the manually-written version of the handler with name `n`, if it exists, to the automatically generated one
                 

                resolve-handler

                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.routes.home.html b/documentation/youyesyet.routes.home.html index 24cd05f..2e54c1f 100644 --- a/documentation/youyesyet.routes.home.html +++ b/documentation/youyesyet.routes.home.html @@ -1,5 +1,5 @@ -youyesyet.routes.home documentation

                youyesyet.routes.home

                Routes/pages available to unauthenticated users.
                +youyesyet.routes.home documentation

                youyesyet.routes.home

                Routes/pages available to unauthenticated users.
                 

                about-page

                (about-page)
                FIXME: write docs

                call-me-page

                (call-me-page request)
                FIXME: write docs

                home-page

                (home-page)
                FIXME: write docs

                home-routes

                FIXME: write docs

                login-page

                (login-page request)
                This is very temporary. We're going to do authentication by oauth.
                 

                motd

                (motd)
                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.routes.issue-experts.html b/documentation/youyesyet.routes.issue-experts.html index 2cceafd..20f7479 100644 --- a/documentation/youyesyet.routes.issue-experts.html +++ b/documentation/youyesyet.routes.issue-experts.html @@ -1,6 +1,6 @@ -youyesyet.routes.issue-experts documentation

                youyesyet.routes.issue-experts

                Routes/pages available to issue experts.
                +youyesyet.routes.issue-experts documentation

                youyesyet.routes.issue-experts

                Routes/pages available to issue experts.
                 

                get-and-lock-followuprequest!

                (get-and-lock-followuprequest! id user)
                Return the `followuprequest` record indicated by this `id`, provided that
                 it is unlocked. As a side effect, lock it to this `user`.

                get-followup-request-page

                (get-followup-request-page request)
                FIXME: write docs

                issue-expert-routes

                FIXME: write docs

                list-page

                (list-page request)
                FIXME: write docs

                post-followup-action

                (post-followup-action request)
                From this `request`, create a `followupaction` record, and, if an
                 `option_id` is present in the params, an `intention` record; show
                diff --git a/documentation/youyesyet.routes.manual.html b/documentation/youyesyet.routes.manual.html
                index 9417a27..7d1a732 100644
                --- a/documentation/youyesyet.routes.manual.html
                +++ b/documentation/youyesyet.routes.manual.html
                @@ -1,4 +1,4 @@
                 
                -youyesyet.routes.manual documentation

                youyesyet.routes.manual

                Manual overrides for auto-generated routes
                +youyesyet.routes.manual documentation

                youyesyet.routes.manual

                Manual overrides for auto-generated routes
                 
                \ No newline at end of file diff --git a/documentation/youyesyet.routes.oauth.html b/documentation/youyesyet.routes.oauth.html index db60c55..2711a8f 100644 --- a/documentation/youyesyet.routes.oauth.html +++ b/documentation/youyesyet.routes.oauth.html @@ -1,6 +1,6 @@ -youyesyet.routes.oauth documentation

                youyesyet.routes.oauth

                OAuth authentication routes - not finished, does not work yet.
                +youyesyet.routes.oauth documentation

                youyesyet.routes.oauth

                OAuth authentication routes - not finished, does not work yet.
                 

                oauth-callback

                (oauth-callback request_token {:keys [session]})
                Handles the callback from the authority.
                 

                oauth-init

                (oauth-init request)
                Initiates the OAuth with the authority implied by this `request`
                 

                oauth-routes

                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.routes.rest.html b/documentation/youyesyet.routes.rest.html index 88bfd58..46f599e 100644 --- a/documentation/youyesyet.routes.rest.html +++ b/documentation/youyesyet.routes.rest.html @@ -1,6 +1,6 @@ -youyesyet.routes.rest documentation

                youyesyet.routes.rest

                Manually maintained routes which handle data transfer to/from the canvasser app.
                +youyesyet.routes.rest documentation

                youyesyet.routes.rest

                Manually maintained routes which handle data transfer to/from the canvasser app.
                 

                create-intention-and-visit!

                (create-intention-and-visit! request)
                Doing visit creation logic server side; request params are expected to
                 include an `option_id`, an `elector_id` and an `address_id`, or an `option` and
                 a `location`. If no `address_id` is provided, we simply create an
                diff --git a/documentation/youyesyet.routes.roles.html b/documentation/youyesyet.routes.roles.html
                index ab04f64..5cd69b6 100644
                --- a/documentation/youyesyet.routes.roles.html
                +++ b/documentation/youyesyet.routes.roles.html
                @@ -1,6 +1,6 @@
                 
                -youyesyet.routes.roles documentation

                youyesyet.routes.roles

                Routes/pages available to authenticated users in specific roles.
                +youyesyet.routes.roles documentation

                youyesyet.routes.roles

                Routes/pages available to authenticated users in specific roles.
                 

                admins-page

                (admins-page request)
                FIXME: write docs

                analysts-page

                (analysts-page request)
                My expectation is that analysts will do a lot of their work through QGIS or
                 some other geographical information system; so there isn't a need to put
                 anything sophisticated here.

                canvassers-page

                (canvassers-page request)
                FIXME: write docs

                roles-page

                (roles-page request)
                FIXME: write docs

                roles-routes

                FIXME: write docs

                team-organisers-page

                (team-organisers-page request)
                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.routes.services.html b/documentation/youyesyet.routes.services.html index 29cf074..87024de 100644 --- a/documentation/youyesyet.routes.services.html +++ b/documentation/youyesyet.routes.services.html @@ -1,4 +1,4 @@ -youyesyet.routes.services documentation

                youyesyet.routes.services

                REST API.
                +youyesyet.routes.services documentation

                youyesyet.routes.services

                REST API.
                 

                service-routes

                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.utils.html b/documentation/youyesyet.utils.html index 2286012..c2d655d 100644 --- a/documentation/youyesyet.utils.html +++ b/documentation/youyesyet.utils.html @@ -1,3 +1,3 @@ -youyesyet.utils documentation

                youyesyet.utils

                FIXME: write docs

                coerce-to-number

                (coerce-to-number v)
                FIXME: write docs
                \ No newline at end of file +youyesyet.utils documentation

                youyesyet.utils

                FIXME: write docs

                coerce-to-number

                (coerce-to-number v)
                FIXME: write docs
                \ No newline at end of file diff --git a/documentation/youyesyet.validation.html b/documentation/youyesyet.validation.html index 17fb005..74382ff 100644 --- a/documentation/youyesyet.validation.html +++ b/documentation/youyesyet.validation.html @@ -1,3 +1,3 @@ -youyesyet.validation documentation

                youyesyet.validation

                FIXME: write docs
                \ No newline at end of file +youyesyet.validation documentation

                youyesyet.validation

                FIXME: write docs
                \ No newline at end of file diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 017463e..40b815d 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -87,8 +87,9 @@ ORDER BY roles.name, roles.id -- :name list-elector-intentions :? :* --- :doc short form of `list-intentions-by-elector`, returning far less data, --- for use in `youyesyet.routes.rest/get-local-data`, q.v. +-- :doc short form of `list-intentions-by-elector`, returning far less data, for use in `youyesyet.routes.rest/get-local-data`, q.v. +-- TODO: should be limited to visits in the past 24 hours, to prevent the app being +-- used to harrass NO voters. See https://github.com/simon-brooke/youyesyet/issues/58 SELECT intentions.id, intentions.option_id, visits.date FROM intentions, visits WHERE intentions.visit_id = visits.id diff --git a/src/clj/youyesyet/authorisation.clj b/src/clj/youyesyet/authorisation.clj index 2ca529b..8b13789 100644 --- a/src/clj/youyesyet/authorisation.clj +++ b/src/clj/youyesyet/authorisation.clj @@ -1,4 +1 @@ -(ns ^{:doc "Field-level authorisation. Messy." - :author "Simon Brooke"} - youyesyet.authorisation - (:require [youyesyet.env :refer [defaults]])) + diff --git a/src/cljs/youyesyet/canvasser_app/handlers.cljs b/src/cljs/youyesyet/canvasser_app/handlers.cljs index d857bc2..80bd7e6 100644 --- a/src/cljs/youyesyet/canvasser_app/handlers.cljs +++ b/src/cljs/youyesyet/canvasser_app/handlers.cljs @@ -51,6 +51,16 @@ :anchor nil)) +(defn handle-forbidden + "If response has status 403 (forbidden) redirect to the login page." + [response & forms] + (if + (= (str (:status response)) "403") + (do + (js/console.log "Forbidden! redirecting") + (set! (.-location js/document) "/login")) + (apply 'do forms))) + (defn compose-packet [item] "Convert this `item` into a URI which can be sent as a GET call" @@ -197,10 +207,12 @@ ;; TODO: signal something has failed? It doesn't matter very much, unless it keeps failing. (js/console.log (str "Failed to fetch locality data" response)) ;; loop to do it again - (dispatch [:dispatch-later [{:ms 60000 :dispatch [:fetch-locality]}]]) - {:db (assoc - (remove-from-feedback db :fetch-locality) - :error (cons :fetch-locality (:error db)))})) + (handle-forbidden + response + (dispatch [:dispatch-later [{:ms 60000 :dispatch [:fetch-locality]}]]) + {:db (assoc + (remove-from-feedback db :fetch-locality) + :error (cons :fetch-locality (:error db)))}))) (reg-event-fx From e7432e62a462bd79a24528380ce2af5f75819ce2 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 23 Sep 2018 17:33:59 +0100 Subject: [PATCH 04/17] Upversioned to 0.2.2-SNAPSHOT; fixed map menu --- project.clj | 2 +- resources/public/css/yyy-app.css | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 87b02f2..b085bfa 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject youyesyet "0.2.1" +(defproject youyesyet "0.2.2-SNAPSHOT" :description "Canvassing tool for referenda" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version" diff --git a/resources/public/css/yyy-app.css b/resources/public/css/yyy-app.css index 0277205..439b02b 100644 --- a/resources/public/css/yyy-app.css +++ b/resources/public/css/yyy-app.css @@ -45,7 +45,6 @@ h1 { width: 100%; _position: absolute; _top: expression(document.documentElement.scrollTop); - z-index: 149; background:rgba(7,27,51,0.8); } @@ -57,6 +56,7 @@ h1 { #nav-menu { margin: 0; padding: 0; + z-index: 02110-1301; } #nav menu li { @@ -103,6 +103,7 @@ h1 { #nav:hover #nav-menu { display: block; list-style-type: none; + z-index: 148; width: 100%; } @@ -141,7 +142,7 @@ h1 { margin: 0; padding: 0; position: fixed; - z-index: 149; + z-index: 600; color: silver; background:rgba(40,40,40,0.9); } @@ -149,6 +150,7 @@ h1 { #nav:hover #nav-menu { display: block; list-style-type: none; + z-index: 500; width: 100%; } From 35920a256b027728e7f1780e116d19c97f925695 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 23 Sep 2018 18:40:01 +0100 Subject: [PATCH 05/17] Got the library serving on the right URL! --- resources/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/templates/base.html b/resources/templates/base.html index 950e3e8..521f77c 100644 --- a/resources/templates/base.html +++ b/resources/templates/base.html @@ -27,7 +27,7 @@ Menu
              1. Home
              2. -
              3. Library
              4. +
              5. Library
              6. {% if user %}
              7. Roles
              8. Logout
              9. From b94fa5596d302ea96861365cc45d361f8e3901e1 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 23 Sep 2018 19:17:00 +0100 Subject: [PATCH 06/17] Added the wee 'about cookies' feature from Smeagol --- resources/public/css/yyy-common.css | 2 +- resources/templates/base.html | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/resources/public/css/yyy-common.css b/resources/public/css/yyy-common.css index 288243a..62cc890 100644 --- a/resources/public/css/yyy-common.css +++ b/resources/public/css/yyy-common.css @@ -212,7 +212,7 @@ th { width: 30%; float: right; position: fixed; - bottom: 3.5em; + bottom: 2em; right: 0; z-index: 175; background: transparent; diff --git a/resources/templates/base.html b/resources/templates/base.html index 521f77c..92e2a75 100644 --- a/resources/templates/base.html +++ b/resources/templates/base.html @@ -68,6 +68,19 @@ {% endblock %}
                +
                +
                + This website stores session information as a 'cookie' on your browser. + This helps us show you the content you want to see. This cookie does + not identify you, and cannot be read by other websites. It is deleted + by your browser as soon as you leave this site. This website does not + use any third party cookies, so your visit here cannot be tracked by + other websites. +
                +
                + About cookies +
                +

                {% block foot %} From b21c275157d70c04f139970d950d0c2c913749a0 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 30 Sep 2018 14:48:20 +0100 Subject: [PATCH 07/17] Ignore new auto-generated file 'cache.clj' --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2096dbc..3d04b99 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ youyesyet\.canonical\.adl\.xml youyesyet\.dump\.20180816 *.tar + +src/clj/youyesyet/cache\.clj From b9f685b528bea17e1ccde8cbeb15efed32854376 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 2 Oct 2018 15:01:57 +0100 Subject: [PATCH 08/17] Brought README up to date with current develop It was badly out of date and actively misleading. --- README.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 74f4f00..714542e 100644 --- a/README.md +++ b/README.md @@ -45,17 +45,27 @@ You should also read the [User-Oriented Specification](doc/specification/userspe ## Building this -This application is built using [Application Description Language](); the intention is that soon Application Description Language will run as a Leiningen plugin, but that does not yet work. +This application is built using [Application Description Language](https://github.com/simon-brooke/adl/). The `adl` pre-processor is run as a prep task to building the `uberjar`, which in turn is preparatory to building the `uberwar`. -So first you must check out the Application Description Language repository as well as this repository, ideally within a a common directory; +This will generate a large number of the source files required by YouYesYet, **including** the database initialisation scripts. These generated source files are not, as a matter of policy, held in the repository. -then: +### What is auto-generated, and how to override it - cd adl - lein uberjar - java -jar target/adl-1.4.4-SNAPSHOT-standalone.jar --path ../youyesyet/ ../youyesyet/youyesyet.adl.xml +The following files are generated from the master file `youyesyet.adl.xml`: + +* `resources/sql/queries.auto.sql` - [HugSQL](https://www.hugsql.org/) queries for selection, insertion, modification and deletion of records of all entities described in the ADL file. +* `resources/sql/[application-name].postgres.sql` - [Postgres](https://www.postgresql.org/) database initialisation script including tables for all entities, convenience views for all entities, all necessary link tables and referential integrity constraints. +* `resources/templates/auto/*.html` - [Selmer](https://github.com/yogthos/Selmer) templates for each form or list list specified in the ADL file (pages are not yet handled). +* `src/clj/[application-name]/routes/auto.clj` - [Compojure]() routes for each form or list list specified in the ADL file (pages are not yet handled). +* `src/clj/[application-name]/routes/auto-json.clj` - [Compojure]() routes returning JSON responses for each query generated in `resources/sql/queries.auto.sql`. + +*You are strongly advised never to edit any of these files*. + +* To override any query, add that query to a file `resources/sql/queries.sql` +* To add additional material (for example reference data) to the database initialisation, add it to a separate file or a family of separate files. +* To override any template, copy the template file from `resources/templates/auto/` to `resources/templates/` and edit it there. +* To override any route, write a function of the same name in the namespace `[application-name].routes.manual`. -This will generate a large number of the source files required by YouYesYet, **including** the database initialisation scripts. ## Getting the database up @@ -73,7 +83,7 @@ Do get the database initialised, run createdb youyesyet_dev -I'm no longer using Migratus as I'm using [Application Description Language]() +I'm no longer using Migratus as I'm using [Application Description Language](https://github.com/simon-brooke/adl/) to generate the majority of the application, and, as changes are made to the application description, new database schemas are generated. The database initialisation script will be found at `resources/sql/youyesyet.postgres.sql`. Manually maintained overrides are found in @@ -118,7 +128,12 @@ which will aid in work on the ClojureScript components. ## Running in a production environment -Doesn't really work yet; if you want to try it, see [Bug #36](https://github.com/simon-brooke/youyesyet/issues/36) and check out the associated feature branch. +Either + +1. run `lein uberjar` and execute the resulting jar file directly; or +2. run `lein uberwar` and serve the resulting war file from a servlet container. + +The [beta production server](https://www.projecthope.scot/) currently runs an uberwar build in Tomcat behind Nginx. ## Working on this project @@ -149,7 +164,7 @@ Note that all tools recommended in this document are free for non-commercial use ### Editors/IDEs -I (Simon) use, like and recommend [LightTable](http://lighttable.com/) as my editor; I used to use Emacs, and there is excellent Clojure tooling for Emacs, but these days Emacs ways of working seem just too far from everything else to be comfortable to me. [NightCode](https://sekao.net/nightcode/) is a lighter-weight Clojure IDE which you may like. There's also [Cursive](https://cursive-ide.com/) but it isn't free and I haven't tried it. +I (Simon) use, like and recommend [LightTable](http://lighttable.com/) as my editor; I used to use Emacs, and there is excellent Clojure tooling for Emacs, but these days Emacs ways of working seem just too far from everything else to be comfortable to me. [NightCode](https://sekao.net/nightcode/) is a lighter-weight Clojure IDE which you may like. There's also [Cursive](https://cursive-ide.com/) but it isn't free and I have so far found it more annoying than helpful; or [Counterclockwise](https://github.com/ccw-ide/ccw) which I don't have recent experience of. ### Git From 918a8205e9a81dc8ef1dfab1cb9f7f712c221968 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 2 Oct 2018 18:36:57 +0100 Subject: [PATCH 09/17] Updated adl-support dependency to current develop --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index b085bfa..b63695f 100644 --- a/project.clj +++ b/project.clj @@ -5,7 +5,7 @@ :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :url "https://github.com/simon-brooke/youyesyet" - :dependencies [[adl-support "0.1.4"] + :dependencies [[adl-support "0.1.6-SNAPSHOT"] [bouncer "1.0.1"] [ch.qos.logback/logback-classic "1.2.3"] [clj-oauth "1.5.5"] From e411eb38b2f5483759a58f905e7836cdfa92d00c Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 4 Oct 2018 10:59:11 +0100 Subject: [PATCH 10/17] Fixed the 'duplicate canvassers role' issue... And the cause wasn't at all what I thought. --- resources/sql/queries.sql | 2 +- src/clj/youyesyet/routes/roles.clj | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index 40b815d..ff0f3b5 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -79,7 +79,7 @@ AND visits.id = :id -- :name list-roles-by-canvasser :? :* -- :doc links all existing canvasser records related to a given role -SELECT roles.* +SELECT DISTINCT roles.* FROM roles, ln_canvassers_roles WHERE roles.id = ln_canvassers_roles.role_id AND ln_canvassers_roles.canvasser_id = :id diff --git a/src/clj/youyesyet/routes/roles.clj b/src/clj/youyesyet/routes/roles.clj index 7f7ba3d..39a5946 100644 --- a/src/clj/youyesyet/routes/roles.clj +++ b/src/clj/youyesyet/routes/roles.clj @@ -24,13 +24,14 @@ user (db-core/list-roles-by-canvasser db-core/*db* {:id (:id user)}))] (log/info (str "Roles routing page; user is " user "; roles are " roles)) - (cond - roles (layout/render "roles.html" - {:title (str "Welcome " (:fullname user) ", what do you want to do?") - :user user - :roles (map #(assoc % :link (safe-name (:name %) :sql)) roles)}) - (empty? roles)(response/found "/app") - true (assoc (response/found "/login") :session (dissoc session :user))))) + (if + roles + (layout/render + "roles.html" + {:title (str "Welcome " (:fullname user) ", what do you want to do?") + :user user + :roles (map #(assoc % :link (safe-name (:name %) :sql)) roles)}) + (assoc (response/found "/login") :session (dissoc session :user))))) (defn admins-page From 316c57745be17218d26f553aad314e903fbfc144 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 4 Oct 2018 19:16:24 +0100 Subject: [PATCH 11/17] Multi-select menus now working Major change, there may be regressions. --- resources/sql/queries.sql | 10 ---------- src/clj/youyesyet/routes/rest.clj | 3 ++- youyesyet.adl.xml | 29 ++++++++++++++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index ff0f3b5..e5404bf 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -75,16 +75,6 @@ FROM addresses, visits WHERE visits.address_id = addresses.id AND visits.id = :id --- I don't know why this next one isn't autogenerating, but it isn't and it's critical. - --- :name list-roles-by-canvasser :? :* --- :doc links all existing canvasser records related to a given role -SELECT DISTINCT roles.* -FROM roles, ln_canvassers_roles -WHERE roles.id = ln_canvassers_roles.role_id - AND ln_canvassers_roles.canvasser_id = :id -ORDER BY roles.name, - roles.id -- :name list-elector-intentions :? :* -- :doc short form of `list-intentions-by-elector`, returning far less data, for use in `youyesyet.routes.rest/get-local-data`, q.v. diff --git a/src/clj/youyesyet/routes/rest.clj b/src/clj/youyesyet/routes/rest.clj index 351926e..39851bd 100644 --- a/src/clj/youyesyet/routes/rest.clj +++ b/src/clj/youyesyet/routes/rest.clj @@ -72,7 +72,8 @@ (db/list-elector-intentions db/*db* {:id (:id e)}))) (db/list-electors-by-dwelling db/*db* {:id (:id d)})))) (db/list-dwellings-by-address db/*db* {:id (:id a)})))) - addresses))) + addresses) + )) :ttl/threshold 90000)) diff --git a/youyesyet.adl.xml b/youyesyet.adl.xml index 820514d..78d310d 100644 --- a/youyesyet.adl.xml +++ b/youyesyet.adl.xml @@ -188,6 +188,7 @@ version="0.1.1"> building. + @@ -297,9 +298,7 @@ version="0.1.1"> - + All visits made by canvassers to dwellings in @@ -363,9 +362,7 @@ version="0.1.1"> - + Authorities which may authenticate canvassers to @@ -413,6 +410,7 @@ version="0.1.1"> + Issues believed to be of interest to electors, @@ -776,9 +774,22 @@ version="0.1.1"> - - - + + + + + + + + + + + + + + From 3f0491d4c5d799a8be64620ffffa00e60a92ee0d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 4 Oct 2018 22:52:51 +0100 Subject: [PATCH 12/17] Work on auxlists --- youyesyet.adl.xml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/youyesyet.adl.xml b/youyesyet.adl.xml index 78d310d..6645e4b 100644 --- a/youyesyet.adl.xml +++ b/youyesyet.adl.xml @@ -777,7 +777,7 @@ version="0.1.1"> - + @@ -855,12 +855,6 @@ version="0.1.1"> - - - - - - @@ -880,7 +874,9 @@ version="0.1.1"> - + From 71d81748698bd6e989a746fb40c3caa92af2d643 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 13 Mar 2019 15:08:26 +0000 Subject: [PATCH 13/17] Capitalise names of icons, since that seems to be the decision we've made. --- src/cljs/youyesyet/canvasser_app/gis.cljs | 4 ++-- src/cljs/youyesyet/canvasser_app/views/elector.cljs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cljs/youyesyet/canvasser_app/gis.cljs b/src/cljs/youyesyet/canvasser_app/gis.cljs index 3dd3389..1cd9c28 100644 --- a/src/cljs/youyesyet/canvasser_app/gis.cljs +++ b/src/cljs/youyesyet/canvasser_app/gis.cljs @@ -2,7 +2,7 @@ :author "Simon Brooke"} youyesyet.canvasser-app.gis (:require [cljs.reader :refer [read-string]] - [clojure.string :refer [lower-case]] + [clojure.string :refer [capitalize lower-case]] [cemerick.url :refer (url url-encode)] [day8.re-frame.http-fx] [re-frame.core :refer [dispatch reg-event-db reg-event-fx subscribe]] @@ -74,7 +74,7 @@ (:dwellings address))))))] (case (count intentions) 0 "unknown-pin" - 1 (lower-case (str (name (first intentions)) "-pin")) + 1 (capitalize (lower-case (str (name (first intentions)) "-pin"))) "mixed-pin"))) diff --git a/src/cljs/youyesyet/canvasser_app/views/elector.cljs b/src/cljs/youyesyet/canvasser_app/views/elector.cljs index 119ac44..4929574 100644 --- a/src/cljs/youyesyet/canvasser_app/views/elector.cljs +++ b/src/cljs/youyesyet/canvasser_app/views/elector.cljs @@ -1,7 +1,8 @@ (ns ^{:doc "Canvasser app single elector panel." :author "Simon Brooke"} youyesyet.canvasser-app.views.elector - (:require [reagent.core :refer [atom]] + (:require [clojure.string :refer [capitalize]] + [reagent.core :refer [atom]] [re-frame.core :refer [reg-sub subscribe dispatch]] [youyesyet.canvasser-app.ui-utils :as ui])) @@ -51,7 +52,7 @@ "Generate a row showing this `option` for this elector." [elector option] (let [optid (:id option) - optname (name optid)] + optname (capitalize (name optid))] [:tr {:key (str "options-" optname)} (let [selected (= optid (:intention elector)) image (if selected (str "img/option/" optname "-selected.png") From ae0c38336543e149523bdec29b163a2c9d4c38e1 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 2 May 2019 10:25:35 +0100 Subject: [PATCH 14/17] Intentionally added documentation to repository So that it can be automatically deployed to github.io --- docs/authorisation.html | 19 + docs/competitors.html | 14 + docs/css/default.css | 551 ++++++++++++++++++ docs/css/highlight.css | 97 +++ docs/database.html | 173 ++++++ docs/index.html | 3 + docs/js/highlight.min.js | 2 + docs/js/jquery.min.js | 4 + docs/js/page_effects.js | 112 ++++ docs/scaling.html | 108 ++++ docs/userspec.html | 151 +++++ docs/youyesyet.cache.html | 3 + docs/youyesyet.canvasser-app.ajax.html | 3 + docs/youyesyet.canvasser-app.core.html | 3 + docs/youyesyet.canvasser-app.gis.html | 3 + docs/youyesyet.canvasser-app.handlers.html | 3 + docs/youyesyet.canvasser-app.state.html | 3 + ...youyesyet.canvasser-app.subscriptions.html | 3 + docs/youyesyet.canvasser-app.ui-utils.html | 3 + docs/youyesyet.canvasser-app.views.about.html | 3 + ...ouyesyet.canvasser-app.views.building.html | 3 + ...ouyesyet.canvasser-app.views.dwelling.html | 3 + ...youyesyet.canvasser-app.views.elector.html | 3 + ...ouyesyet.canvasser-app.views.followup.html | 3 + docs/youyesyet.canvasser-app.views.gdpr.html | 3 + docs/youyesyet.canvasser-app.views.issue.html | 3 + .../youyesyet.canvasser-app.views.issues.html | 3 + docs/youyesyet.canvasser-app.views.map.html | 3 + docs/youyesyet.config.html | 3 + docs/youyesyet.db.core.html | 3 + docs/youyesyet.handler.html | 3 + docs/youyesyet.layout.html | 3 + docs/youyesyet.locality.html | 3 + docs/youyesyet.middleware.html | 3 + docs/youyesyet.oauth.html | 3 + docs/youyesyet.outqueue.html | 3 + docs/youyesyet.routes.auto-json.html | 3 + docs/youyesyet.routes.auto.html | 3 + docs/youyesyet.routes.home.html | 3 + docs/youyesyet.routes.issue-experts.html | 3 + docs/youyesyet.routes.manual.html | 3 + docs/youyesyet.routes.oauth.html | 3 + docs/youyesyet.routes.rest.html | 3 + docs/youyesyet.routes.roles.html | 3 + docs/youyesyet.routes.services.html | 3 + docs/youyesyet.utils.html | 3 + docs/youyesyet.validation.html | 3 + project.clj | 9 +- 48 files changed, 1348 insertions(+), 3 deletions(-) create mode 100644 docs/authorisation.html create mode 100644 docs/competitors.html create mode 100644 docs/css/default.css create mode 100644 docs/css/highlight.css create mode 100644 docs/database.html create mode 100644 docs/index.html create mode 100644 docs/js/highlight.min.js create mode 100644 docs/js/jquery.min.js create mode 100644 docs/js/page_effects.js create mode 100644 docs/scaling.html create mode 100644 docs/userspec.html create mode 100644 docs/youyesyet.cache.html create mode 100644 docs/youyesyet.canvasser-app.ajax.html create mode 100644 docs/youyesyet.canvasser-app.core.html create mode 100644 docs/youyesyet.canvasser-app.gis.html create mode 100644 docs/youyesyet.canvasser-app.handlers.html create mode 100644 docs/youyesyet.canvasser-app.state.html create mode 100644 docs/youyesyet.canvasser-app.subscriptions.html create mode 100644 docs/youyesyet.canvasser-app.ui-utils.html create mode 100644 docs/youyesyet.canvasser-app.views.about.html create mode 100644 docs/youyesyet.canvasser-app.views.building.html create mode 100644 docs/youyesyet.canvasser-app.views.dwelling.html create mode 100644 docs/youyesyet.canvasser-app.views.elector.html create mode 100644 docs/youyesyet.canvasser-app.views.followup.html create mode 100644 docs/youyesyet.canvasser-app.views.gdpr.html create mode 100644 docs/youyesyet.canvasser-app.views.issue.html create mode 100644 docs/youyesyet.canvasser-app.views.issues.html create mode 100644 docs/youyesyet.canvasser-app.views.map.html create mode 100644 docs/youyesyet.config.html create mode 100644 docs/youyesyet.db.core.html create mode 100644 docs/youyesyet.handler.html create mode 100644 docs/youyesyet.layout.html create mode 100644 docs/youyesyet.locality.html create mode 100644 docs/youyesyet.middleware.html create mode 100644 docs/youyesyet.oauth.html create mode 100644 docs/youyesyet.outqueue.html create mode 100644 docs/youyesyet.routes.auto-json.html create mode 100644 docs/youyesyet.routes.auto.html create mode 100644 docs/youyesyet.routes.home.html create mode 100644 docs/youyesyet.routes.issue-experts.html create mode 100644 docs/youyesyet.routes.manual.html create mode 100644 docs/youyesyet.routes.oauth.html create mode 100644 docs/youyesyet.routes.rest.html create mode 100644 docs/youyesyet.routes.roles.html create mode 100644 docs/youyesyet.routes.services.html create mode 100644 docs/youyesyet.utils.html create mode 100644 docs/youyesyet.validation.html diff --git a/docs/authorisation.html b/docs/authorisation.html new file mode 100644 index 0000000..c808f3c --- /dev/null +++ b/docs/authorisation.html @@ -0,0 +1,19 @@ + +Security and authorisation

                Security and authorisation

                +

                Essentially we have six levels of authorisation, at essentially increasing levels of sensitivity.

                +
                  +
                1. +

                  Canvassers: Any authenticated user essentially has this level of authorisation. Hence users of the app can all share the same database connections without problem. Therefore there will be one first-class database user for all canvassers, and they will not have individual real database logins.

                2. +
                3. +

                  Issue experts: Issue experts respond to followup requests. Therefore they must be able to see the queue of requests and the details of the elector making the request. They don’t need to see voter intentions and I don’t believe the information they do need to see is particularly sensitive. So they too can share a single database-layer login and connection pool; whether this is the same login as used by the canvassers is an implementation detail but I don’t believe that it’s critical.

                4. +
                5. +

                  Issue editors: Don’t need to see much sensitive data (although they do need to see, in aggregate, what issues are being raised by electors in the field), but they do have the power to dictate the initial responses canvassers make to issues raised, so the information they can write is pretty sensitive. We need to be very sure that unauthorised users don’t have the power to write this data. So I suggest that issue editors probably should have individual first class database logins.

                6. +
                7. +

                  Team leaders: Need to be able to monitor the performance of their teams, to invite new users to the system and to block abusive users from the system. Again, these are significant functions which should be well protected from abuse. But we will have at least hundreds, probably thousands of team leaders across Scotland. I would prefer that they each had first class logins, but this may be impractical. But in any case, even if they use a shared login, it should not be the same shared login as used by canvassers.

                8. +
                9. +

                  Analyists Need broad authorisation to read, but not write or edit, all sensitive data held by the system. They must have individual first class database logins.

                10. +
                11. +

                  Admins Can necessarily read and write everything. They should definitely each have individual first class database logins.

                12. +
                +

                This means we have a hybrid authentication scheme; for lower levels, application layer security and shared connection pools are adequate. For higher levels, individual connections and database layer authorisation are required. It implies that the routes at the different layers should be separated into separate namespaces with separate authentication functions.

                \ No newline at end of file diff --git a/docs/competitors.html b/docs/competitors.html new file mode 100644 index 0000000..88b37dd --- /dev/null +++ b/docs/competitors.html @@ -0,0 +1,14 @@ + +Competitor Analysis

                Competitor Analysis

                +

                Obviously You Yes Yet? is my baby; I’ve put a lot of thought into it. At the time I started working on it I wasn’t aware of any open source competitors; I did to a web search, and I emailed the Bernie Sanders campaign to see whether their widely admired tools were open source. I didn’t find anything.

                +

                However, I’ve just been pointed to Vote Leave’s Vics tool, and there may well be others.

                +

                There is no room here for ego. What matters is that the Yes campaign gets the best available tool for the job. So it’s important to do competitor analysis, and not to invest too much work into You Yes Yet? unless there’s a realistic possibility of producing a tool which is better than any of the available alternatives. But it’s also the case that by studying competitors we may find ways to improve the design of You Yes Yet?.

                +

                Vics

                +

                Vics, the Voter Intention Collection System, is reputed to have been a significant factor in the successful campaign by Vote Leave to take Britain out of the EU. It has been released as open source under MIT licence, so it is unambiguously available for us to use.

                +

                The architecture comprises a single-page app built using Angular talking to a server built in Java using the Spring framework. The database engine used is Postgres. Jedis, a Java port of Redis, is used as an in-memory data cache, server side.

                +

                ### Download and initial build

                +

                I checked out the source from the GitHub repository, and following the instructions in the README created the database and ran a maven install process. Unfortunately, run as a normal user, when this process goes into its test sequence many tests fail unable to contact Jedis. I find it slightly worrying to run such a large and complex build as root, but as root it gets substantially further. The build still doesn’t complete but it seems that it is closer to completion.

                +

                The ironic point is that it fails because it depends on the JavaScript package manager bower, and bower (very sensibly) refuses to run as root. I therefore made a small modification to the build script to allow it to run bower as root, but unfortunately that didn’t solve the build problem; the jedis service was still not found where it was expected.

                +

                This is difficult to diagnose; the exception is so deeply nested in framework code that no code from the actual Vics application appears on the stack dump, which makes it very hard to know where to start in debugging.

                +

                So for tonight I’ve failed. I shall try again.

                \ No newline at end of file diff --git a/docs/css/default.css b/docs/css/default.css new file mode 100644 index 0000000..33f78fe --- /dev/null +++ b/docs/css/default.css @@ -0,0 +1,551 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 15px; +} + +pre, code { + font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; + font-size: 9pt; + margin: 15px 0; +} + +h1 { + font-weight: normal; + font-size: 29px; + margin: 10px 0 2px 0; + padding: 0; +} + +h2 { + font-weight: normal; + font-size: 25px; +} + +h5.license { + margin: 9px 0 22px 0; + color: #555; + font-weight: normal; + font-size: 12px; + font-style: italic; +} + +.document h1, .namespace-index h1 { + font-size: 32px; + margin-top: 12px; +} + +#header, #content, .sidebar { + position: fixed; +} + +#header { + top: 0; + left: 0; + right: 0; + height: 22px; + color: #f5f5f5; + padding: 5px 7px; +} + +#content { + top: 32px; + right: 0; + bottom: 0; + overflow: auto; + background: #fff; + color: #333; + padding: 0 18px; +} + +.sidebar { + position: fixed; + top: 32px; + bottom: 0; + overflow: auto; +} + +.sidebar.primary { + background: #e2e2e2; + border-right: solid 1px #cccccc; + left: 0; + width: 250px; +} + +.sidebar.secondary { + background: #f2f2f2; + border-right: solid 1px #d7d7d7; + left: 251px; + width: 200px; +} + +#content.namespace-index, #content.document { + left: 251px; +} + +#content.namespace-docs { + left: 452px; +} + +#content.document { + padding-bottom: 10%; +} + +#header { + background: #3f3f3f; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); + z-index: 100; +} + +#header h1 { + margin: 0; + padding: 0; + font-size: 18px; + font-weight: lighter; + text-shadow: -1px -1px 0px #333; +} + +#header h1 .project-version { + font-weight: normal; +} + +.project-version { + padding-left: 0.15em; +} + +#header a, .sidebar a { + display: block; + text-decoration: none; +} + +#header a { + color: #f5f5f5; +} + +.sidebar a { + color: #333; +} + +#header h2 { + float: right; + font-size: 9pt; + font-weight: normal; + margin: 4px 3px; + padding: 0; + color: #bbb; +} + +#header h2 a { + display: inline; +} + +.sidebar h3 { + margin: 0; + padding: 10px 13px 0 13px; + font-size: 19px; + font-weight: lighter; +} + +.sidebar h3 a { + color: #444; +} + +.sidebar h3.no-link { + color: #636363; +} + +.sidebar ul { + padding: 7px 0 6px 0; + margin: 0; +} + +.sidebar ul.index-link { + padding-bottom: 4px; +} + +.sidebar li { + display: block; + vertical-align: middle; +} + +.sidebar li a, .sidebar li .no-link { + border-left: 3px solid transparent; + padding: 0 10px; + white-space: nowrap; +} + +.sidebar li .no-link { + display: block; + color: #777; + font-style: italic; +} + +.sidebar li .inner { + display: inline-block; + padding-top: 7px; + height: 24px; +} + +.sidebar li a, .sidebar li .tree { + height: 31px; +} + +.depth-1 .inner { padding-left: 2px; } +.depth-2 .inner { padding-left: 6px; } +.depth-3 .inner { padding-left: 20px; } +.depth-4 .inner { padding-left: 34px; } +.depth-5 .inner { padding-left: 48px; } +.depth-6 .inner { padding-left: 62px; } + +.sidebar li .tree { + display: block; + float: left; + position: relative; + top: -10px; + margin: 0 4px 0 0; + padding: 0; +} + +.sidebar li.depth-1 .tree { + display: none; +} + +.sidebar li .tree .top, .sidebar li .tree .bottom { + display: block; + margin: 0; + padding: 0; + width: 7px; +} + +.sidebar li .tree .top { + border-left: 1px solid #aaa; + border-bottom: 1px solid #aaa; + height: 19px; +} + +.sidebar li .tree .bottom { + height: 22px; +} + +.sidebar li.branch .tree .bottom { + border-left: 1px solid #aaa; +} + +.sidebar.primary li.current a { + border-left: 3px solid #a33; + color: #a33; +} + +.sidebar.secondary li.current a { + border-left: 3px solid #33a; + color: #33a; +} + +.namespace-index h2 { + margin: 30px 0 0 0; +} + +.namespace-index h3 { + font-size: 16px; + font-weight: bold; + margin-bottom: 0; +} + +.namespace-index .topics { + padding-left: 30px; + margin: 11px 0 0 0; +} + +.namespace-index .topics li { + padding: 5px 0; +} + +.namespace-docs h3 { + font-size: 18px; + font-weight: bold; +} + +.public h3 { + margin: 0; + float: left; +} + +.usage { + clear: both; +} + +.public { + margin: 0; + border-top: 1px solid #e0e0e0; + padding-top: 14px; + padding-bottom: 6px; +} + +.public:last-child { + margin-bottom: 20%; +} + +.members .public:last-child { + margin-bottom: 0; +} + +.members { + margin: 15px 0; +} + +.members h4 { + color: #555; + font-weight: normal; + font-variant: small-caps; + margin: 0 0 5px 0; +} + +.members .inner { + padding-top: 5px; + padding-left: 12px; + margin-top: 2px; + margin-left: 7px; + border-left: 1px solid #bbb; +} + +#content .members .inner h3 { + font-size: 12pt; +} + +.members .public { + border-top: none; + margin-top: 0; + padding-top: 6px; + padding-bottom: 0; +} + +.members .public:first-child { + padding-top: 0; +} + +h4.type, +h4.dynamic, +h4.added, +h4.deprecated { + float: left; + margin: 3px 10px 15px 0; + font-size: 15px; + font-weight: bold; + font-variant: small-caps; +} + +.public h4.type, +.public h4.dynamic, +.public h4.added, +.public h4.deprecated { + font-size: 13px; + font-weight: bold; + margin: 3px 0 0 10px; +} + +.members h4.type, +.members h4.added, +.members h4.deprecated { + margin-top: 1px; +} + +h4.type { + color: #717171; +} + +h4.dynamic { + color: #9933aa; +} + +h4.added { + color: #508820; +} + +h4.deprecated { + color: #880000; +} + +.namespace { + margin-bottom: 30px; +} + +.namespace:last-child { + margin-bottom: 10%; +} + +.index { + padding: 0; + font-size: 80%; + margin: 15px 0; + line-height: 16px; +} + +.index * { + display: inline; +} + +.index p { + padding-right: 3px; +} + +.index li { + padding-right: 5px; +} + +.index ul { + padding-left: 0; +} + +.type-sig { + clear: both; + color: #088; +} + +.type-sig pre { + padding-top: 10px; + margin: 0; +} + +.usage code { + display: block; + color: #008; + margin: 2px 0; +} + +.usage code:first-child { + padding-top: 10px; +} + +p { + margin: 15px 0; +} + +.public p:first-child, .public pre.plaintext { + margin-top: 12px; +} + +.doc { + margin: 0 0 26px 0; + clear: both; +} + +.public .doc { + margin: 0; +} + +.namespace-index .doc { + margin-bottom: 20px; +} + +.namespace-index .namespace .doc { + margin-bottom: 10px; +} + +.markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { + line-height: 22px; +} + +.markdown li { + padding: 2px 0; +} + +.markdown h2 { + font-weight: normal; + font-size: 25px; + margin: 30px 0 10px 0; +} + +.markdown h3 { + font-weight: normal; + font-size: 20px; + margin: 30px 0 0 0; +} + +.markdown h4 { + font-size: 15px; + margin: 22px 0 -4px 0; +} + +.doc, .public, .namespace .index { + max-width: 680px; + overflow-x: visible; +} + +.markdown pre > code { + display: block; + padding: 10px; +} + +.markdown pre > code, .src-link a { + border: 1px solid #e4e4e4; + border-radius: 2px; +} + +.markdown code:not(.hljs), .src-link a { + background: #f6f6f6; +} + +pre.deps { + display: inline-block; + margin: 0 10px; + border: 1px solid #e4e4e4; + border-radius: 2px; + padding: 10px; + background-color: #f6f6f6; +} + +.markdown hr { + border-style: solid; + border-top: none; + color: #ccc; +} + +.doc ul, .doc ol { + padding-left: 30px; +} + +.doc table { + border-collapse: collapse; + margin: 0 10px; +} + +.doc table td, .doc table th { + border: 1px solid #dddddd; + padding: 4px 6px; +} + +.doc table th { + background: #f2f2f2; +} + +.doc dl { + margin: 0 10px 20px 10px; +} + +.doc dl dt { + font-weight: bold; + margin: 0; + padding: 3px 0; + border-bottom: 1px solid #ddd; +} + +.doc dl dd { + padding: 5px 0; + margin: 0 0 5px 10px; +} + +.doc abbr { + border-bottom: 1px dotted #333; + font-variant: none; + cursor: help; +} + +.src-link { + margin-bottom: 15px; +} + +.src-link a { + font-size: 70%; + padding: 1px 4px; + text-decoration: none; + color: #5555bb; +} diff --git a/docs/css/highlight.css b/docs/css/highlight.css new file mode 100644 index 0000000..d0cdaa3 --- /dev/null +++ b/docs/css/highlight.css @@ -0,0 +1,97 @@ +/* +github.com style (c) Vasily Polovnyov +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/docs/database.html b/docs/database.html new file mode 100644 index 0000000..7078934 --- /dev/null +++ b/docs/database.html @@ -0,0 +1,173 @@ + +Database Specification

                Database Specification

                +

                Note that this is a work in progress. Read it in concert with the Entity-Relationship Diagram.

                +

                Tables are listed in alphabetical order.

                +

                Address

                +

                The postal address of a building which contains at least one dwelling at which electors are registered.

                +
                CREATE TABLE IF NOT EXISTS addresses (
                +    id integer NOT NULL,
                +    address character varying(256) NOT NULL,
                +    postcode character varying(16),
                +    phone character varying(16),
                +    district_id integer,
                +    latitude real,
                +    longitude real
                +);
                +
                +

                Authority

                +

                An oauth authority which authenticates canvassers. Note that there will need to be substantially more in this table but I don’t yet know what.

                +
                CREATE TABLE IF NOT EXISTS authorities (
                +    id character varying(32) NOT NULL
                +);
                +
                +

                Canvasser

                +

                A user of the system.

                +
                CREATE TABLE IF NOT EXISTS canvassers (
                +    id serial,
                +    username character varying(32) NOT NULL,
                +    fullname character varying(64) NOT NULL,
                +    elector_id integer,
                +    address_id integer NOT NULL,
                +    phone character varying(16),
                +    email character varying(128),
                +    authority_id character varying(32) NOT NULL,
                +    authorised boolean
                +);
                +
                +

                District

                +

                An electoral district.

                +
                CREATE TABLE IF NOT EXISTS districts (
                +    id integer NOT NULL,
                +    name character varying(64) NOT NULL
                +);
                +
                +

                Dwelling

                +

                A dwelling at which electors are registered. Most addresses obviously have only one dwelling, but in flatted buildings there will be multiple dwellings. The sub_address field contains information to distinguish the dwelling, e.g. ‘flat 2.1’.

                +
                CREATE TABLE IF NOT EXISTS dwellings (
                +    id serial NOT NULL primary key,
                +    address_id integer NOT NULL references addresses(id),
                +    sub_address varchar(16)
                +);
                +
                +

                Elector

                +

                Someone entitled to cast a vote in the referendum.

                +
                CREATE TABLE IF NOT EXISTS electors (
                +    id integer NOT NULL,
                +    name character varying(64) NOT NULL,
                +    dwelling_id integer NOT NULL,
                +    phone character varying(16),
                +    email character varying(128)
                +);
                +
                +

                Followup Action

                +

                An action performed by an issue expert in response to a followup request.

                +
                CREATE TABLE IF NOT EXISTS followupactions (
                +    id integer NOT NULL,
                +    request_id integer NOT NULL,
                +    actor integer NOT NULL,
                +    date timestamp with time zone DEFAULT now() NOT NULL,
                +    notes text,
                +    closed boolean
                +);
                +
                +

                Followup Method

                +

                A method for responding to a followup request; reference data.

                +
                CREATE TABLE IF NOT EXISTS followupmethods (
                +    id character varying(32) NOT NULL
                +);
                +
                +insert into followupmethods values ('Telephone');
                +insert into followupmethods values ('eMail');
                +insert into followupmethods values ('Post');
                +
                +

                Followup Request

                +

                A request recorded by a canvasser for an issue expert to contact an elector with regard to a particular issue.

                +
                CREATE TABLE IF NOT EXISTS followuprequests (
                +    id integer NOT NULL,
                +    elector_id integer NOT NULL,
                +    visit_id integer NOT NULL,
                +    issue_id character varying(32) NOT NULL,
                +    method_id character varying(32) NOT NULL
                +);
                +
                +

                Intention

                +

                An intention, by an elector, to vote for an option; captured by a canvasser during a visit.

                +
                CREATE TABLE IF NOT EXISTS intentions (
                +    id serial not null,
                +    elector integer not null references elector(id),
                +    option varchar(32) not null references option(id),
                +    visit integer not null references visit(id),
                +    date timestamp with time zone DEFAULT now() NOT NULL
                +);
                +
                +

                Issue

                +

                An issue which might affect electors’ decisions regarding their intention.

                +
                CREATE TABLE IF NOT EXISTS issues (
                +    id character varying(32) NOT NULL,
                +    url character varying(256),
                +    content varchar(1024),
                +    current default false
                +);
                +
                +

                Issue expertise

                +

                Expertise of a canvasser able to use a method, in an issue.

                +
                CREATE TABLE IF NOT EXISTS issueexpertise (
                +    canvasser_id integer NOT NULL,
                +    issue_id character varying(32) NOT NULL,
                +    method_id character varying(32) NOT NULL
                +);
                +
                +

                Option

                +

                An option for which an elector may have an intention to vote.

                +
                CREATE TABLE IF NOT EXISTS options (
                +    id character varying(32) NOT NULL
                +);
                +
                +

                Role

                +

                A role (other than basic Canvasser) that a user may have in the system. Reference data.

                +
                create table if not exists roles (
                +  id serial primary key,
                +  name varchar(64) not null
                +);
                +
                +

                Role Member

                +

                Membership of a user (*Canvasser*) of an additional role; link table.

                +
                create table if not exists rolememberships (
                +  role_id integer not null references roles(id),
                +  canvasser_id integer not null references canvassers(id)
                +);
                +
                +

                Team

                +

                A team of canvassers in a locality who are known to one another and frequently canvas together.

                +
                create table if not exists teams (
                +  id serial primary key,
                +  name varchar(64) not null,
                +  district_id integer not null references districts(id),
                +  latitude real,
                +  longitude real
                +);
                +
                +

                Team Member

                +

                Membership of a user (*Canvasser*) of a particular team. Canvassers may join multiple teams. Link table.

                +
                create table if not exists teammemberships (
                +  team_id integer not null references teams(id),
                +  canvasser_id integer not null references canvassers(id)
                +);
                +
                +

                Team Organiser

                +

                A relationship which defines a user (*Canvasser*) as an organiser of a team. A team may have more than one organiser. An organiser (if they also have the role ‘Recruiter’, which they often will have) may recruit additional Canvassers as members of their team, or accept applications by canvassers to join their team. An organiser may promote a member of the team to organiser of the team, and may also exclude a member from the team.

                +
                create table if not exists teamorganiserships (
                +  team_id integer not null references teams(id),
                +  canvasser_id integer not null references canvassers(id)
                +);
                +
                +

                Visit

                +

                A visit by a canvasser to an address on a date to solicit intentions from electors.

                +
                CREATE TABLE IF NOT EXISTS visits (
                +    id integer NOT NULL,
                +    address_id integer NOT NULL,
                +    canvasser_id integer NOT NULL,
                +    date timestamp with time zone DEFAULT now() NOT NULL
                +);
                +
                \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..6b279cf --- /dev/null +++ b/docs/index.html @@ -0,0 +1,3 @@ + +Youyesyet 0.2.2-SNAPSHOT

                Youyesyet 0.2.2-SNAPSHOT

                Released under the GNU General Public License,version 2.0 or (at your option) any later version

                Canvassing tool for referenda.

                Installation

                To install, add the following dependency to your project or build file:

                [youyesyet "0.2.2-SNAPSHOT"]

                Topics

                Namespaces

                youyesyet.canvasser-app.ajax

                Canvasser app transciever for ajax packets.

                Public variables and functions:

                youyesyet.canvasser-app.state

                Canvasser app client state.

                Public variables and functions:

                youyesyet.canvasser-app.subscriptions

                Canvasser app event subscriptions.

                Public variables and functions:

                  youyesyet.canvasser-app.ui-utils

                  Canvasser app user interface widgets.

                  youyesyet.canvasser-app.views.about

                  Canvasser app about panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.building

                  Canvasser app households in building panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.dwelling

                  Canvasser app electors in household panel.

                  youyesyet.canvasser-app.views.elector

                  Canvasser app single elector panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.followup

                  Canvasser followup request form panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.gdpr

                  Canvasser app electors in household panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.issue

                  Canvasser app current issue detail panel.

                  Public variables and functions:

                  youyesyet.canvasser-app.views.issues

                  Canvasser app current issues list panel.

                  Public variables and functions:

                  youyesyet.config

                  Read configuration.

                  Public variables and functions:

                  youyesyet.db.core

                  Database access functions, mostly from Luminus template.

                  Public variables and functions:

                  youyesyet.handler

                  Handlers for starting and stopping the webapp.

                  Public variables and functions:

                  youyesyet.layout

                  Render web pages using Selmer templating markup.

                  youyesyet.locality

                  TODO: write docs

                  Public variables and functions:

                  youyesyet.locality

                  Public variables and functions:

                  youyesyet.middleware

                  Plumbing, mainly boilerplate from Luminus.

                  youyesyet.oauth

                  Handle oauth with multiple authenticating authorities.

                  youyesyet.outqueue

                  Queue of messages waiting to be sent to the server.

                  youyesyet.outqueue

                  Queue of messages waiting to be sent to the server.

                  youyesyet.routes.auto

                  User interface routes for Youyesyet auto-generated by Application Description Language framework at 20190502T100146.996Z

                  Public variables and functions:

                  youyesyet.routes.auto-json

                  JSON routes for youyesyet auto-generated by Application Description Language framework at 20190502T100145.444Z

                  Public variables and functions:

                  youyesyet.routes.home

                  Routes/pages available to unauthenticated users.

                  Public variables and functions:

                  youyesyet.routes.logged-in

                  Routes/pages available to authenticated users.

                  Public variables and functions:

                  youyesyet.routes.manual

                  Manual overrides for auto-generated routes

                  Public variables and functions:

                    youyesyet.routes.oauth

                    OAuth authentication routes - not finished, does not work yet.

                    Public variables and functions:

                    youyesyet.routes.roles

                    Routes/pages available to authenticated users in specific roles.

                    youyesyet.routes.services

                    REST API.

                    Public variables and functions:

                    youyesyet.utils

                    TODO: write docs

                    Public variables and functions:

                    youyesyet.utils

                    Public variables and functions:

                    youyesyet.validation

                    TODO: write docs

                    Public variables and functions:

                      youyesyet.validation

                      Public variables and functions:

                        \ No newline at end of file diff --git a/docs/js/highlight.min.js b/docs/js/highlight.min.js new file mode 100644 index 0000000..6486ffd --- /dev/null +++ b/docs/js/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
                        ":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
                        ",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); \ No newline at end of file diff --git a/docs/js/jquery.min.js b/docs/js/jquery.min.js new file mode 100644 index 0000000..73f33fb --- /dev/null +++ b/docs/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
                        ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
                        a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
                        ","
                        "],area:[1,"",""],param:[1,"",""],thead:[1,"","
                        "],tr:[2,"","
                        "],col:[2,"","
                        "],td:[3,"","
                        "],_default:l.htmlSerialize?[0,"",""]:[1,"X
                        ","
                        "]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("