Merge tag 'youyesyet-0.2.2'
This commit is contained in:
commit
ef786024d7
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -47,3 +47,5 @@ youyesyet\.canonical\.adl\.xml
|
|||
youyesyet\.dump\.20180816
|
||||
|
||||
*.tar
|
||||
|
||||
src/clj/youyesyet/cache\.clj
|
||||
|
|
35
README.md
35
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
|
||||
|
||||
|
|
19
docs/authorisation.html
Normal file
19
docs/authorisation.html
Normal file
File diff suppressed because one or more lines are too long
14
docs/competitors.html
Normal file
14
docs/competitors.html
Normal file
File diff suppressed because one or more lines are too long
551
docs/css/default.css
Normal file
551
docs/css/default.css
Normal file
|
@ -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;
|
||||
}
|
97
docs/css/highlight.css
Normal file
97
docs/css/highlight.css
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
*/
|
||||
|
||||
.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;
|
||||
}
|
173
docs/database.html
Normal file
173
docs/database.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/index.html
Normal file
3
docs/index.html
Normal file
File diff suppressed because one or more lines are too long
2
docs/js/highlight.min.js
vendored
Normal file
2
docs/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
docs/js/jquery.min.js
vendored
Normal file
4
docs/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
112
docs/js/page_effects.js
Normal file
112
docs/js/page_effects.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
function visibleInParent(element) {
|
||||
var position = $(element).position().top
|
||||
return position > -50 && position < ($(element).offsetParent().height() - 50)
|
||||
}
|
||||
|
||||
function hasFragment(link, fragment) {
|
||||
return $(link).attr("href").indexOf("#" + fragment) != -1
|
||||
}
|
||||
|
||||
function findLinkByFragment(elements, fragment) {
|
||||
return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first()
|
||||
}
|
||||
|
||||
function scrollToCurrentVarLink(elements) {
|
||||
var elements = $(elements);
|
||||
var parent = elements.offsetParent();
|
||||
|
||||
if (elements.length == 0) return;
|
||||
|
||||
var top = elements.first().position().top;
|
||||
var bottom = elements.last().position().top + elements.last().height();
|
||||
|
||||
if (top >= 0 && bottom <= parent.height()) return;
|
||||
|
||||
if (top < 0) {
|
||||
parent.scrollTop(parent.scrollTop() + top);
|
||||
}
|
||||
else if (bottom > parent.height()) {
|
||||
parent.scrollTop(parent.scrollTop() + bottom - parent.height());
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentVarLink() {
|
||||
$('.secondary a').parent().removeClass('current')
|
||||
$('.anchor').
|
||||
filter(function(index) { return visibleInParent(this) }).
|
||||
each(function(index, element) {
|
||||
findLinkByFragment(".secondary a", element.id).
|
||||
parent().
|
||||
addClass('current')
|
||||
});
|
||||
scrollToCurrentVarLink('.secondary .current');
|
||||
}
|
||||
|
||||
var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }())
|
||||
|
||||
function scrollPositionId(element) {
|
||||
var directory = window.location.href.replace(/[^\/]+\.html$/, '')
|
||||
return 'scroll::' + $(element).attr('id') + '::' + directory
|
||||
}
|
||||
|
||||
function storeScrollPosition(element) {
|
||||
if (!hasStorage) return;
|
||||
localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft())
|
||||
localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop())
|
||||
}
|
||||
|
||||
function recallScrollPosition(element) {
|
||||
if (!hasStorage) return;
|
||||
$(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x"))
|
||||
$(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y"))
|
||||
}
|
||||
|
||||
function persistScrollPosition(element) {
|
||||
recallScrollPosition(element)
|
||||
$(element).scroll(function() { storeScrollPosition(element) })
|
||||
}
|
||||
|
||||
function sidebarContentWidth(element) {
|
||||
var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() })
|
||||
return Math.max.apply(Math, widths)
|
||||
}
|
||||
|
||||
function calculateSize(width, snap, margin, minimum) {
|
||||
if (width == 0) {
|
||||
return 0
|
||||
}
|
||||
else {
|
||||
return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2))
|
||||
}
|
||||
}
|
||||
|
||||
function resizeSidebars() {
|
||||
var primaryWidth = sidebarContentWidth('.primary')
|
||||
var secondaryWidth = 0
|
||||
|
||||
if ($('.secondary').length != 0) {
|
||||
secondaryWidth = sidebarContentWidth('.secondary')
|
||||
}
|
||||
|
||||
// snap to grid
|
||||
primaryWidth = calculateSize(primaryWidth, 32, 13, 160)
|
||||
secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160)
|
||||
|
||||
$('.primary').css('width', primaryWidth)
|
||||
$('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1)
|
||||
|
||||
if (secondaryWidth > 0) {
|
||||
$('#content').css('left', primaryWidth + secondaryWidth + 2)
|
||||
}
|
||||
else {
|
||||
$('#content').css('left', primaryWidth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
$(window).ready(resizeSidebars)
|
||||
$(window).ready(setCurrentVarLink)
|
||||
$(window).ready(function() { persistScrollPosition('.primary')})
|
||||
$(window).ready(function() {
|
||||
$('#content').scroll(setCurrentVarLink)
|
||||
$(window).resize(setCurrentVarLink)
|
||||
})
|
108
docs/scaling.html
Normal file
108
docs/scaling.html
Normal file
File diff suppressed because one or more lines are too long
151
docs/userspec.html
Normal file
151
docs/userspec.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.cache.html
Normal file
3
docs/youyesyet.cache.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.ajax.html
Normal file
3
docs/youyesyet.canvasser-app.ajax.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.core.html
Normal file
3
docs/youyesyet.canvasser-app.core.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.gis.html
Normal file
3
docs/youyesyet.canvasser-app.gis.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.handlers.html
Normal file
3
docs/youyesyet.canvasser-app.handlers.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.state.html
Normal file
3
docs/youyesyet.canvasser-app.state.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.subscriptions.html
Normal file
3
docs/youyesyet.canvasser-app.subscriptions.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.ui-utils.html
Normal file
3
docs/youyesyet.canvasser-app.ui-utils.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.about.html
Normal file
3
docs/youyesyet.canvasser-app.views.about.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.building.html
Normal file
3
docs/youyesyet.canvasser-app.views.building.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.dwelling.html
Normal file
3
docs/youyesyet.canvasser-app.views.dwelling.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.elector.html
Normal file
3
docs/youyesyet.canvasser-app.views.elector.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.followup.html
Normal file
3
docs/youyesyet.canvasser-app.views.followup.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.gdpr.html
Normal file
3
docs/youyesyet.canvasser-app.views.gdpr.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.issue.html
Normal file
3
docs/youyesyet.canvasser-app.views.issue.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.issues.html
Normal file
3
docs/youyesyet.canvasser-app.views.issues.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.canvasser-app.views.map.html
Normal file
3
docs/youyesyet.canvasser-app.views.map.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.config.html
Normal file
3
docs/youyesyet.config.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.db.core.html
Normal file
3
docs/youyesyet.db.core.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.handler.html
Normal file
3
docs/youyesyet.handler.html
Normal file
File diff suppressed because one or more lines are too long
4
docs/youyesyet.layout.html
Normal file
4
docs/youyesyet.layout.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.locality.html
Normal file
3
docs/youyesyet.locality.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.middleware.html
Normal file
3
docs/youyesyet.middleware.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.oauth.html
Normal file
3
docs/youyesyet.oauth.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.outqueue.html
Normal file
3
docs/youyesyet.outqueue.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.auto-json.html
Normal file
3
docs/youyesyet.routes.auto-json.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.auto.html
Normal file
3
docs/youyesyet.routes.auto.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.home.html
Normal file
3
docs/youyesyet.routes.home.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.issue-experts.html
Normal file
3
docs/youyesyet.routes.issue-experts.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.manual.html
Normal file
3
docs/youyesyet.routes.manual.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.oauth.html
Normal file
3
docs/youyesyet.routes.oauth.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.rest.html
Normal file
3
docs/youyesyet.routes.rest.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.roles.html
Normal file
3
docs/youyesyet.routes.roles.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.routes.services.html
Normal file
3
docs/youyesyet.routes.services.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.utils.html
Normal file
3
docs/youyesyet.utils.html
Normal file
File diff suppressed because one or more lines are too long
3
docs/youyesyet.validation.html
Normal file
3
docs/youyesyet.validation.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
72
project.clj
72
project.clj
|
@ -1,54 +1,55 @@
|
|||
(defproject youyesyet "0.2.1"
|
||||
(defproject youyesyet "0.2.2"
|
||||
|
||||
:description "Canvassing tool for referenda"
|
||||
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
|
||||
: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"]
|
||||
[bouncer "1.0.1"]
|
||||
[ch.qos.logback/logback-classic "1.2.3"]
|
||||
[clj-oauth "1.5.5"]
|
||||
[cljsjs/react-leaflet "1.6.5-0"]
|
||||
[cljs-ajax "0.7.4"]
|
||||
;; [cljsjs/react-leaflet "2.0.1-0"] is available but doesn't seem to work fully
|
||||
[cljs-ajax "0.8.0"]
|
||||
[com.andrewmcveigh/cljs-time "0.5.2"]
|
||||
[clojure.java-time "0.3.2"]
|
||||
[com.cemerick/url "0.1.1"]
|
||||
[compojure "1.6.1"]
|
||||
[conman "0.8.2"]
|
||||
[cprop "0.1.11"]
|
||||
[conman "0.8.3"]
|
||||
[cprop "0.1.13"]
|
||||
[day8.re-frame/http-fx "0.1.6"]
|
||||
[korma "0.4.3"]
|
||||
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
|
||||
[luminus/ring-ttl-session "0.3.2"]
|
||||
[luminus-nrepl "0.1.4"]
|
||||
[luminus-migrations "0.5.2"]
|
||||
[luminus-immutant "0.2.4"]
|
||||
[markdown-clj "1.0.2"]
|
||||
[luminus-nrepl "0.1.6"]
|
||||
[luminus-migrations "0.6.5"]
|
||||
[luminus-immutant "0.2.5"]
|
||||
[markdown-clj "1.0.8"]
|
||||
[metosin/compojure-api "1.1.12"]
|
||||
[metosin/ring-http-response "0.9.0"]
|
||||
[migratus "1.0.8"]
|
||||
[mount "0.1.12"]
|
||||
[metosin/ring-http-response "0.9.1"]
|
||||
[migratus "1.2.3"]
|
||||
[mount "0.1.16"]
|
||||
[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.339" :scope "provided"]
|
||||
[org.clojure/clojurescript "1.10.520" :scope "provided"]
|
||||
[org.clojure/core.memoize "0.7.1"]
|
||||
;;[org.clojure/spec.alpha "0.2.168"]
|
||||
[org.clojure/tools.cli "0.3.7"]
|
||||
[org.clojure/tools.cli "0.4.2"]
|
||||
[org.clojure/tools.logging "0.4.1"]
|
||||
[org.postgresql/postgresql "42.2.4"]
|
||||
[org.webjars/bootstrap "4.1.2"]
|
||||
[org.webjars/font-awesome "5.1.0"]
|
||||
[org.postgresql/postgresql "42.2.5"]
|
||||
[org.webjars/bootstrap "4.3.1"]
|
||||
[org.webjars/font-awesome "5.8.1"]
|
||||
[org.webjars.bower/tether "1.4.4"]
|
||||
[postgre-types "0.0.4"]
|
||||
[re-frame "0.10.5"]
|
||||
[re-frame "0.10.6"]
|
||||
[reagent "0.8.1"]
|
||||
[reagent-utils "0.3.1"]
|
||||
[ring-middleware-format "0.7.2"]
|
||||
[reagent-utils "0.3.2"]
|
||||
[ring-middleware-format "0.7.4"]
|
||||
[ring/ring-defaults "0.3.2"]
|
||||
[ring/ring-servlet "1.6.3"]
|
||||
[ring/ring-servlet "1.7.1"]
|
||||
[ring-webjars "0.2.0"]
|
||||
[secretary "1.2.3"]
|
||||
[selmer "1.11.8"]]
|
||||
[selmer "1.12.12"]]
|
||||
|
||||
:deploy-repositories [["releases" :clojars]
|
||||
["snapshots" :clojars]]
|
||||
|
@ -63,9 +64,9 @@
|
|||
:main ^:skip-aot youyesyet.core
|
||||
:migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")}
|
||||
|
||||
:plugins [[lein-adl "0.1.6"]
|
||||
:plugins [[lein-adl "0.1.7"]
|
||||
[lein-cljsbuild "1.1.7"]
|
||||
[lein-codox "0.10.4"]
|
||||
[lein-codox "0.10.7-multilang"]
|
||||
[lein-cprop "1.0.3"]
|
||||
[lein-kibit "0.1.6"]
|
||||
[lein-less "1.7.5"]
|
||||
|
@ -77,10 +78,13 @@
|
|||
|
||||
:cucumber-feature-paths ["test/clj/features"]
|
||||
|
||||
:codox {:metadata {:doc "FIXME: write docs"}
|
||||
:codox {:metadata {:doc "**TODO**: write docs"
|
||||
:doc/format :markdown}
|
||||
:languages [:clojure :clojurescript]
|
||||
:source-paths ["src/clj" "src/cljc" "src/cljs"]
|
||||
:output-path "documentation"}
|
||||
:source-uri "https://github.com/simon-brooke/youyesyet/blob/master/{filepath}#L{line}"
|
||||
:output-path "docs"}
|
||||
|
||||
|
||||
:npm {:dependencies [[datatables.net "1.10.19"]
|
||||
[datatables.net-dt "1.10.19"]
|
||||
|
@ -144,21 +148,21 @@
|
|||
|
||||
:test [:project/dev :project/test :profiles/test]
|
||||
|
||||
:project/dev {:dependencies [[prone "1.1.4"]
|
||||
[ring/ring-mock "0.3.2"]
|
||||
[ring/ring-devel "1.6.3"]
|
||||
:project/dev {:dependencies [[prone "1.6.3"]
|
||||
[ring/ring-mock "0.4.0"]
|
||||
[ring/ring-devel "1.7.1"]
|
||||
[org.webjars/webjars-locator-jboss-vfs "0.1.0"]
|
||||
[luminus-immutant "0.2.4"]
|
||||
[pjstadig/humane-test-output "0.8.3"]
|
||||
[luminus-immutant "0.2.5"]
|
||||
[pjstadig/humane-test-output "0.9.0"]
|
||||
[binaryage/devtools "0.9.10"]
|
||||
[com.cemerick/piggieback "0.2.2"]
|
||||
[directory-naming/naming-java "0.8"]
|
||||
[doo "0.1.10"]
|
||||
[figwheel-sidecar "0.5.16"]]
|
||||
[doo "0.1.11"]
|
||||
[figwheel-sidecar "0.5.18"]]
|
||||
:plugins [[com.jakemccrary/lein-test-refresh "0.23.0"]
|
||||
[lein-doo "0.1.10"]
|
||||
[lein-figwheel "0.5.16"]
|
||||
[org.clojure/clojurescript "1.9.495"]]
|
||||
[org.clojure/clojurescript "1.10.520"]]
|
||||
:cljsbuild {:builds
|
||||
{:app
|
||||
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
||||
|
|
|
@ -212,7 +212,7 @@ th {
|
|||
width: 30%;
|
||||
float: right;
|
||||
position: fixed;
|
||||
bottom: 3.5em;
|
||||
bottom: 2em;
|
||||
right: 0;
|
||||
z-index: 175;
|
||||
background: transparent;
|
||||
|
|
|
@ -26,13 +26,6 @@
|
|||
</header>
|
||||
|
||||
<div id="main-container" class="container">
|
||||
<div id="content">
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="col-lg-12">
|
||||
<div class="centering text-center">
|
||||
<div class="text-center">
|
||||
<h2><span class="text-danger">Error: 502 Bad Gateway</span></h2>
|
||||
<p>
|
||||
We are suffering an intermittent problem causing an occasional crash of
|
||||
|
@ -43,12 +36,6 @@
|
|||
The application will restart automatically
|
||||
within five minutes, please take a short break.
|
||||
</p>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br clear="both"/>
|
||||
|
|
|
@ -75,13 +75,13 @@ 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 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.
|
||||
-- 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
|
||||
AND intentions.elector_id = :id
|
||||
ORDER BY visits.date DESC
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<img id="nav-icon" src="{{servlet-context}}/img/threelines.png" alt="Menu"/>
|
||||
<menu id="nav-menu" class="nav">
|
||||
<li class=""><a href="{{servlet-context}}/home">Home</a></li>
|
||||
<li class=""><a href="{{servlet-context}}/library">Library</a></li>
|
||||
<li class=""><a href="https://library.projecthope.scot/">Library</a></li>
|
||||
{% if user %}
|
||||
<li class=""><a href="{{servlet-context}}/roles">Roles</a></li>
|
||||
<li class=""><a href="{{servlet-context}}/logout">Logout</a></li>
|
||||
|
@ -68,6 +68,19 @@
|
|||
<!-- content: put your main page content into this block -->
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div id="cookies">
|
||||
<div id="more-about-cookies">
|
||||
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.
|
||||
</div>
|
||||
<div id="about-cookies">
|
||||
About cookies
|
||||
</div>
|
||||
</div>
|
||||
<br clear="both"/>
|
||||
</div>
|
||||
{% block foot %}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
(ns ^{:doc "Field-level authorisation. Messy."
|
||||
:author "Simon Brooke"}
|
||||
youyesyet.authorisation
|
||||
(:require [youyesyet.env :refer [defaults]]))
|
|
@ -1,11 +1,14 @@
|
|||
(ns ^{:doc "Read configuration."
|
||||
(ns ^{:doc "Read configuration; largely unaltered from Luminus default."
|
||||
:author "Simon Brooke"}
|
||||
youyesyet.config
|
||||
(:require [cprop.core :refer [load-config]]
|
||||
[cprop.source :as source]
|
||||
[mount.core :refer [args defstate]]))
|
||||
|
||||
(defstate env :start (load-config
|
||||
(defstate env
|
||||
"Configuration, loaded at startup time from properties. **Note** that
|
||||
this conficuration is used only when not running in a Servlet context."
|
||||
:start (load-config
|
||||
:merge
|
||||
[(args)
|
||||
(source/from-system-props)
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
PreparedStatement]))
|
||||
|
||||
(defstate ^:dynamic *db*
|
||||
"Primary connection to the main database. TODO: this does not yet enable
|
||||
sharding."
|
||||
:start (conman/connect! {:jdbc-url-env (env :database-url)
|
||||
:jdbc-url "jdbc:postgresql://127.0.0.1/youyesyet_dev?user=youyesyet&password=thisisnotsecure"
|
||||
:driver-class-name "org.postgresql.Driver"})
|
||||
|
@ -31,7 +33,9 @@
|
|||
(conman/bind-connection *db* "sql/queries.auto.sql" "sql/queries.sql")
|
||||
(hugsql/def-sqlvec-fns "sql/queries.auto.sql")
|
||||
|
||||
(defn to-date [^java.sql.Date sql-date]
|
||||
(defn to-date
|
||||
"Return the SQL date `sql-date` as a Java date."
|
||||
[^java.sql.Date sql-date]
|
||||
(-> sql-date (.getTime) (java.util.Date.)))
|
||||
|
||||
(extend-protocol jdbc/IResultSetReadColumn
|
||||
|
@ -59,7 +63,9 @@
|
|||
(set-parameter [v ^PreparedStatement stmt ^long idx]
|
||||
(.setTimestamp stmt idx (Timestamp. (.getTime v)))))
|
||||
|
||||
(defn to-pg-json [value]
|
||||
(defn to-pg-json
|
||||
"Render this `value` as JavaScript Object Notation."
|
||||
[value]
|
||||
(doto (PGobject.)
|
||||
(.setType "jsonb")
|
||||
(.setValue (generate-string value))))
|
||||
|
|
|
@ -67,6 +67,8 @@
|
|||
|
||||
|
||||
(def app-routes
|
||||
"All routes served as part of the `youyesyet` (server-side) web-app (not
|
||||
to be confused with the client-side `canvasser-app`, q.v.)."
|
||||
(routes
|
||||
(-> #'home-routes
|
||||
(wrap-routes middleware/wrap-csrf)
|
||||
|
@ -101,4 +103,7 @@
|
|||
:message "The page you requested has not yet been implemented"})))))
|
||||
|
||||
|
||||
(def app (middleware/wrap-base #'app-routes))
|
||||
(def app
|
||||
"The `youyesyet` server-side web-app (not to be confused with the client-
|
||||
side `canvasser-app`, q.v.)"
|
||||
(middleware/wrap-base #'app-routes))
|
||||
|
|
|
@ -40,7 +40,15 @@
|
|||
|
||||
|
||||
(declare ^:dynamic *app-context*)
|
||||
(def ^:dynamic *user* nil)
|
||||
;; "Bound to the servlet context, if we're running as a servlet; otherwise
|
||||
;; from configuration. See [[youyesyet.middleware/wrap-context]]."
|
||||
|
||||
(def ^:dynamic *user*
|
||||
"The current user, in circumstances in which we do not have a session.
|
||||
Normally the user is held on a key in the session.
|
||||
|
||||
TODO: is this necessary? Is it *safe*?"
|
||||
nil)
|
||||
|
||||
(parser/set-resource-path! (clojure.java.io/resource "templates"))
|
||||
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
|
||||
|
|
|
@ -69,10 +69,11 @@
|
|||
(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)))
|
||||
addresses)
|
||||
))
|
||||
:ttl/threshold
|
||||
90000))
|
||||
|
||||
|
|
|
@ -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"
|
||||
(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)})
|
||||
(empty? roles)(response/found "/app")
|
||||
true (assoc (response/found "/login") :session (dissoc session :user)))))
|
||||
(assoc (response/found "/login") :session (dissoc session :user)))))
|
||||
|
||||
|
||||
(defn admins-page
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
|
||||
(defn add!
|
||||
"Add this item to the queue."
|
||||
"Add this `item` to the queue `q`."
|
||||
[q item]
|
||||
(swap! q
|
||||
(fn [a]
|
||||
|
@ -54,7 +54,7 @@
|
|||
|
||||
|
||||
(defn queue?
|
||||
"True if x is a queue, else false."
|
||||
"True if `x` is a queue, else false."
|
||||
[x]
|
||||
(try
|
||||
(let [q (deref x)
|
||||
|
@ -68,17 +68,20 @@
|
|||
|
||||
|
||||
(defn peek
|
||||
"Look at the next item which could be removed from the queue."
|
||||
"Look at the next item which could be removed from the queue `q`."
|
||||
[q]
|
||||
(last (:items (deref q))))
|
||||
|
||||
|
||||
(defn locked?
|
||||
"True if this queue `q` is locked, else false."
|
||||
[q]
|
||||
(:locked (deref q)))
|
||||
|
||||
|
||||
(defn unlock!
|
||||
"Unlock the queue `q` if not `value` is supplied; if a `value` is
|
||||
supplied, unlock only if that value is `true`, otherwise lock."
|
||||
([q ]
|
||||
(unlock! q true))
|
||||
([q value]
|
||||
|
@ -86,18 +89,19 @@
|
|||
|
||||
|
||||
(defn lock!
|
||||
"Lock the queue `q`."
|
||||
[q]
|
||||
(unlock! q false))
|
||||
|
||||
|
||||
(defn count
|
||||
"Return the count of items currently in the queue."
|
||||
"Return the count of items currently in the queue `q`."
|
||||
[q]
|
||||
(count (deref q)))
|
||||
|
||||
|
||||
(defn take!
|
||||
"Return the first item from the queue, rebind the queue to the remaining
|
||||
"Return the first item from the queue `q`, rebind the queue to the remaining
|
||||
items. If the queue is empty return nil."
|
||||
[q]
|
||||
(swap! q (fn [a]
|
||||
|
@ -109,8 +113,8 @@
|
|||
|
||||
|
||||
(defn maybe-process-next
|
||||
"Apply this process, assumed to be a function of one argument, to the next
|
||||
item in the queue, if the queue is not currently locked; return the value
|
||||
"Apply this `process`, assumed to be a function of one argument, to the next
|
||||
item in the queue `q`, if the queue is not currently locked; return the value
|
||||
returned by process."
|
||||
[q process]
|
||||
(if (and (queue? q)(not (locked? q)))
|
||||
|
@ -122,5 +126,4 @@
|
|||
(catch #?(:clj Exception :cljs js/Object) any
|
||||
#?(:clj (print (.getMessage any))
|
||||
:cljs (js/console.log (str any))))
|
||||
(finally (unlock! q)))
|
||||
))
|
||||
(finally (unlock! q)))))
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
(defn coerce-to-number [v]
|
||||
"If it is possible to do so, coerce `v` to a number"
|
||||
;; TODO: this doesn't work in cljs.
|
||||
;; TODO: this doesn't work in cljs. Fix if possible.
|
||||
(if (number? v) v
|
||||
(try
|
||||
(read-string (str v))
|
||||
|
|
|
@ -28,9 +28,12 @@
|
|||
|
||||
|
||||
(defn local-uri? [{:keys [uri]}]
|
||||
"Return `true` if the supplied `uri` has no protocol part."
|
||||
(not (re-find #"^\w+?://" (str uri))))
|
||||
|
||||
(defn default-headers [request]
|
||||
"Copy the current uri and cross site request forgery token into the headers
|
||||
of this request."
|
||||
(if (local-uri? request)
|
||||
(-> request
|
||||
(update :uri #(str js/context %))
|
||||
|
|
|
@ -53,33 +53,48 @@
|
|||
(enable-console-print!)
|
||||
|
||||
(defn about-page []
|
||||
"Return the content for the 'about' page."
|
||||
(about/panel))
|
||||
|
||||
(defn building-page []
|
||||
"Return the content for the single building page, for the current address."
|
||||
(building/panel))
|
||||
|
||||
(defn dwelling-page []
|
||||
"Return the content for the single dwelling page, for the current
|
||||
dwelling."
|
||||
(dwelling/panel))
|
||||
|
||||
(defn elector-page []
|
||||
"Return the content for the elector page, for the current dwelling."
|
||||
(elector/panel))
|
||||
|
||||
(defn gdpr-page []
|
||||
"Return the content for the general data protection regulation consent
|
||||
page."
|
||||
(gdpr/panel))
|
||||
|
||||
(defn followup-page []
|
||||
"Return the content for the followup-request page, for the current elector
|
||||
and selected issue."
|
||||
(followup/panel))
|
||||
|
||||
(defn issues-page []
|
||||
"Return the content for the current issues page - list of currently
|
||||
prompted for issues."
|
||||
(issues/panel))
|
||||
|
||||
(defn issue-page []
|
||||
"Return the content for the current issue page: canned text prompt for the
|
||||
canvasser to say to the elector on this issue."
|
||||
(issue/panel))
|
||||
|
||||
(defn map-page []
|
||||
"Return the content for the main map page. Map showing current location."
|
||||
(maps/panel))
|
||||
|
||||
(def pages
|
||||
"Dispatcher table for pages."
|
||||
{:about #'about-page
|
||||
:building #'building-page
|
||||
:dwelling #'dwelling-page
|
||||
|
@ -173,7 +188,9 @@
|
|||
;; -------------------------
|
||||
;; History
|
||||
;; must be called after routes have been defined
|
||||
(defn hook-browser-navigation! []
|
||||
(defn hook-browser-navigation!
|
||||
"Interceptor for the browser back button."
|
||||
[]
|
||||
(doto (History.)
|
||||
(events/listen
|
||||
HistoryEventType/NAVIGATE
|
||||
|
@ -187,7 +204,9 @@
|
|||
(defn mount-components []
|
||||
(r/render [#'page] (.getElementById js/document "app")))
|
||||
|
||||
(defn init! []
|
||||
(defn init!
|
||||
"Initialise the app."
|
||||
[]
|
||||
(rf/dispatch-sync [:initialize-db])
|
||||
(rf/dispatch [:get-current-location])
|
||||
(rf/dispatch [:fetch-locality])
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
:author "Simon Brooke"}
|
||||
youyesyet.canvasser-app.gis
|
||||
(:require [cljs.reader :refer [read-string]]
|
||||
[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]]
|
||||
|
@ -37,7 +38,7 @@
|
|||
;; references, so do it here.
|
||||
|
||||
(defn get-current-location []
|
||||
"Get the current location from the device, setting it in the database and
|
||||
"Return the current location from the device, setting it in the database and
|
||||
returning the locality."
|
||||
(try
|
||||
(if (.-geolocation js/navigator)
|
||||
|
@ -60,19 +61,20 @@
|
|||
|
||||
|
||||
(defn pin-image
|
||||
"select the name of a suitable pin image for this address"
|
||||
"Return the name of a suitable pin image for this `address`."
|
||||
[address]
|
||||
(let [intentions
|
||||
(set
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
:intention
|
||||
(map :option_id
|
||||
(mapcat
|
||||
:intentions
|
||||
(mapcat :electors
|
||||
(:dwellings address)))))]
|
||||
(:dwellings address))))))]
|
||||
(case (count intentions)
|
||||
0 "unknown-pin"
|
||||
1 (str (name (first intentions)) "-pin")
|
||||
1 (capitalize (lower-case (str (name (first intentions)) "-pin")))
|
||||
"mixed-pin")))
|
||||
|
||||
|
||||
|
@ -91,7 +93,7 @@
|
|||
|
||||
|
||||
(defn add-map-pin
|
||||
"Add a map-pin at this address in this map view"
|
||||
"Add an appropriate map-pin at this `address` in this map `view`."
|
||||
[address view]
|
||||
(let [lat (:latitude address)
|
||||
lng (:longitude address)
|
||||
|
@ -108,12 +110,16 @@
|
|||
(.latLng js/L lat lng)
|
||||
(clj->js {:icon pin
|
||||
:title (:address address)}))]
|
||||
(.on (.addTo marker view) "click" (fn [_] (map-pin-click-handler (str (:id address)))))
|
||||
(.on
|
||||
(.addTo marker view)
|
||||
"click"
|
||||
(fn [_] (map-pin-click-handler (str (:id address)))))
|
||||
marker))
|
||||
|
||||
|
||||
(defn map-remove-pins
|
||||
"Remove all pins from this map `view`. Side-effecty; liable to be problematic."
|
||||
"Remove all pins from this map `view`. Side-effecty; liable to be
|
||||
problematic."
|
||||
[view]
|
||||
(if view
|
||||
(.eachLayer view
|
||||
|
@ -124,7 +130,8 @@
|
|||
|
||||
|
||||
(defn refresh-map-pins
|
||||
"Refresh the map pins on this map. Side-effecty; liable to be problematic."
|
||||
"Refresh the map pins on the current map. Side-effecty; liable to be
|
||||
problematic."
|
||||
[]
|
||||
(let [view (map-remove-pins @(subscribe [:view]))
|
||||
addresses @(subscribe [:addresses])]
|
||||
|
|
|
@ -44,13 +44,25 @@
|
|||
(merge state {:error '() :feedback '()}))
|
||||
|
||||
|
||||
(def source-host (assoc
|
||||
(def source-host
|
||||
"The base URL of the host from which the app was loaded."
|
||||
(assoc
|
||||
(url js/window.location)
|
||||
:path "/"
|
||||
:query nil
|
||||
: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"
|
||||
|
@ -76,6 +88,7 @@
|
|||
|
||||
|
||||
(defn add-to-outqueue
|
||||
"Add the supplied `message` to the output queue in this `db`."
|
||||
[db message]
|
||||
(dispatch [:process-queue])
|
||||
(add-to-key db :outqueue message))
|
||||
|
@ -88,6 +101,7 @@
|
|||
|
||||
|
||||
(defn remove-from-key
|
||||
"Remove `x` from the values of key `k` in map `db`."
|
||||
[db k x]
|
||||
(assoc db k (remove #(= x %) (db k))))
|
||||
|
||||
|
@ -99,6 +113,7 @@
|
|||
|
||||
|
||||
(defn remove-from-outqueue
|
||||
"Remove `x` from the output queue in this `db`."
|
||||
[db x]
|
||||
(remove-from-key db :outqueue x))
|
||||
|
||||
|
@ -181,6 +196,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,12 +210,14 @@
|
|||
(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
|
||||
(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)))}))
|
||||
:error (cons :fetch-locality (:error db)))})))
|
||||
|
||||
|
||||
(reg-event-fx
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;; This is the constructor for the atom in which the state of the user interface is held.
|
||||
;;; The atom gets updated by 'events' registered in handler.cljs, q.v.
|
||||
|
||||
|
||||
(def default-db
|
||||
"The default configuration state of the app, when first loaded.
|
||||
This is the constructor for the atom in which the state of the user interface
|
||||
is held. The atom gets updated by 'events' registered in handler.cljs, q.v."
|
||||
{ ;;; any confirmation message to display
|
||||
:feedback '("Welcome to the canvasser app!")
|
||||
;;; message of the day
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defn log-and-dispatch [arg]
|
||||
(js/console.log (str "Dispatching " arg))
|
||||
(rf/dispatch arg))
|
||||
(defn log-and-dispatch [event]
|
||||
"Log this `event` and dispatch it."
|
||||
(js/console.log (str "Dispatching " event))
|
||||
(rf/dispatch event))
|
||||
|
||||
|
||||
(defn back-link
|
||||
|
@ -44,6 +45,8 @@
|
|||
|
||||
|
||||
(defn big-link
|
||||
"Generate a big link with this `text` which, when selected, either opens
|
||||
the url which is this `target` if supplied, or else invokes this `handler`."
|
||||
[text & {:keys [target handler]}]
|
||||
[:div.big-link-container {:key (gensym "big-link")}
|
||||
[:a.big-link (merge {}
|
||||
|
@ -53,6 +56,10 @@
|
|||
|
||||
|
||||
(defn nav-link [uri title page collapsed?]
|
||||
"Generate and return a navigaton link for this `uri` with the text which is
|
||||
this `title`; the `uri` is expected to be the uri of this `page`, and if
|
||||
this `page` is the currently selected page, the lin should be highlighted to
|
||||
indicate this."
|
||||
(let [selected-page @(rf/subscribe [:page])]
|
||||
[:li.nav-item
|
||||
{:class (when (= page selected-page) "active")
|
||||
|
@ -63,6 +70,7 @@
|
|||
|
||||
|
||||
(defn error-panel
|
||||
"Generate and return an error panel with this `message`."
|
||||
[message]
|
||||
[:div
|
||||
[:h1.error message]
|
||||
|
@ -70,7 +78,10 @@
|
|||
(back-link)]])
|
||||
|
||||
|
||||
(defn navbar []
|
||||
(defn navbar
|
||||
"Generate and return a navigation bar representing the current state of the
|
||||
app."
|
||||
[]
|
||||
(r/with-let [collapsed? (r/atom true)]
|
||||
[:div {:id "nav"}
|
||||
[:img {:id "nav-icon"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
:page :gdpr}]))
|
||||
|
||||
|
||||
(defn gender-cell
|
||||
(defn- gender-cell
|
||||
[elector]
|
||||
(let [gender (:gender elector)
|
||||
image (if gender (name gender) "Unknown")]
|
||||
|
@ -56,21 +56,21 @@
|
|||
[:img {:src (str "img/gender/" image ".png") :alt image}]]]))
|
||||
|
||||
|
||||
(defn genders-row
|
||||
(defn- genders-row
|
||||
[electors]
|
||||
[:tr
|
||||
(map
|
||||
#(gender-cell %) electors)])
|
||||
|
||||
|
||||
(defn name-cell
|
||||
(defn- name-cell
|
||||
[elector]
|
||||
[:td {:key (str "name-" (:id elector))
|
||||
:on-click #(go-to-gdpr-for-elector elector)}
|
||||
(:name elector)])
|
||||
|
||||
|
||||
(defn names-row
|
||||
(defn- names-row
|
||||
[electors]
|
||||
[:tr
|
||||
(map
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(ns ^{:doc "Canvasser app electors in household panel."
|
||||
(ns ^{:doc "The General Data Protection Regulations consent panel,
|
||||
incorporating a signature widget."
|
||||
:author "Simon Brooke"}
|
||||
youyesyet.canvasser-app.views.gdpr
|
||||
(:require [re-frame.core :refer [reg-sub subscribe dispatch]]
|
||||
|
@ -31,7 +32,7 @@
|
|||
;; OK, the idea here is a GDPR consent form to be signed by the elector
|
||||
|
||||
(def sig-pad
|
||||
;; something the signature pad will be bound to
|
||||
"An atom that the signature pad will be bound to, when instantiated."
|
||||
(atom nil))
|
||||
|
||||
|
||||
|
@ -52,6 +53,8 @@
|
|||
nil)
|
||||
|
||||
(defn gdpr-render
|
||||
"Return a renderer for the GDPR consent form, incorporating the signature
|
||||
widget."
|
||||
[]
|
||||
(let [elector @(subscribe [:elector])]
|
||||
[:div
|
||||
|
@ -79,12 +82,13 @@
|
|||
|
||||
|
||||
(defn gdpr-did-mount
|
||||
"Instantiate the `sig-pad` atom, q.v."
|
||||
[]
|
||||
(reset! sig-pad (js/SignaturePad. (.getElementById js/document "signature-pad"))))
|
||||
|
||||
|
||||
(defn panel
|
||||
"A reagent class for the GDPR consent form"
|
||||
"Return the GDPR consent form."
|
||||
[]
|
||||
(js/console.log "gdpr.panel")
|
||||
(reagent/create-class {:reagent-render gdpr-render
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
;;; thought.
|
||||
|
||||
;; which provider to use
|
||||
(def *map-provider* :osm)
|
||||
(def ^dynamic *map-provider* :osm)
|
||||
|
||||
(def osm-url "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
|
||||
(def osm-attrib "Map data © <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue