commit 87b351efea567402d9126e06920b578c900d5722 Author: David Baldwynn Date: Mon Jun 29 15:51:29 2015 -0700 first commit diff --git a/.bowerrc b/.bowerrc new file mode 100755 index 00000000..1b842779 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "public/lib" +} diff --git a/.csslintrc b/.csslintrc new file mode 100755 index 00000000..0dab227e --- /dev/null +++ b/.csslintrc @@ -0,0 +1,15 @@ +{ + "adjoining-classes": false, + "box-model": false, + "box-sizing": false, + "floats": false, + "font-sizes": false, + "important": false, + "known-properties": false, + "overqualified-elements": false, + "qualified-headings": false, + "regex-selectors": false, + "unique-headings": false, + "universal-selector": false, + "unqualified-attributes": false +} diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 00000000..ed90a068 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# How-to with your editor: http://editorconfig.org/#download + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +indent_style = tab +insert_final_newline = true + +[{Dockerfile,Procfile}] +trim_trailing_whitespace = true + +# Standard at: https://github.com/felixge/node-style-guide +[{*.js,*.json}] +trim_trailing_whitespace = true +quote_type = single +curly_bracket_next_line = false +spaces_around_operators = true +space_after_control_statements = true +space_after_anonymous_functions = false +spaces_in_brackets = false diff --git a/.gitignore b/.gitignore new file mode 100755 index 00000000..bca129da --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# iOS / Apple +# =========== +.DS_Store +ehthumbs.db +Icon? +Thumbs.db + +# Node and related ecosystem +# ========================== +.nodemonignore +.sass-cache/ +npm-debug.log +node_modules/ +public/lib/ +app/tests/coverage/ +.bower-*/ +.idea/ + +# MEAN.js app and assets +# ====================== +config/sslcerts/*.pem +access.log +public/dist/ + +# Sublime editor +# ============== +.sublime-project +*.sublime-project +*.sublime-workspace + +# Eclipse project files +# ===================== +.project +.settings/ +.*.md.html +.metadata +*~.nib +local.properties + +# IntelliJ +# ======== +*.iml + +# Cloud9 IDE +# ========= +.c9/ +data/ +mongod + +# General +# ======= +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.tmp +*.bak +*.swp +logs/ +build/ diff --git a/.jshintrc b/.jshintrc new file mode 100755 index 00000000..4cd07cdc --- /dev/null +++ b/.jshintrc @@ -0,0 +1,42 @@ +{ + "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. + "browser": true, // Standard browser globals e.g. `window`, `document`. + "esnext": true, // Allow ES.next specific features such as `const` and `let`. + "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). + "camelcase": false, // Permit only camelcase for `var` and `object indexes`. + "curly": false, // Require {} for every new block or scope. + "eqeqeq": true, // Require triple equals i.e. `===`. + "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` + "latedef": true, // Prohibit variable use before definition. + "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`. + "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. + "quotmark": "single", // Define quotes to string values. + "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. + "undef": true, // Require all non-global variables be declared before they are used. + "unused": false, // Warn unused variables. + "strict": true, // Require `use strict` pragma in every file. + "trailing": true, // Prohibit trailing whitespaces. + "smarttabs": false, // Suppresses warnings about mixed tabs and spaces + "globals": { // Globals variables. + "jasmine": true, + "angular": true, + "ApplicationConfiguration": true + }, + "predef": [ // Extra globals. + "define", + "require", + "exports", + "module", + "describe", + "before", + "beforeEach", + "after", + "afterEach", + "it", + "inject", + "expect" + ], + "indent": 4, // Specify indentation spacing + "devel": true, // Allow development statements e.g. `console.log();`. + "noempty": true // Prohibit use of empty blocks. +} \ No newline at end of file diff --git a/.slugignore b/.slugignore new file mode 100755 index 00000000..e4e50baa --- /dev/null +++ b/.slugignore @@ -0,0 +1 @@ +/app/tests \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 00000000..708607e3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "0.10" + - "0.11" +env: + - NODE_ENV=travis +services: + - mongodb diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 00000000..de61a992 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM dockerfile/nodejs + +MAINTAINER Matthias Luebken, matthias@catalyst-zero.com + +WORKDIR /home/mean + +# Install Mean.JS Prerequisites +RUN npm install -g grunt-cli +RUN npm install -g bower + +# Install Mean.JS packages +ADD package.json /home/mean/package.json +RUN npm install + +# Manually trigger bower. Why doesnt this work via npm install? +ADD .bowerrc /home/mean/.bowerrc +ADD bower.json /home/mean/bower.json +RUN bower install --config.interactive=false --allow-root + +# Make everything available for start +ADD . /home/mean + +# currently only works for development +ENV NODE_ENV development + +# Port 3000 for server +# Port 35729 for livereload +EXPOSE 3000 35729 +CMD ["grunt"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 00000000..b366c5dc --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +## License +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100755 index 00000000..3360097a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: ./node_modules/.bin/forever -m 5 server.js diff --git a/README.md b/README.md new file mode 100755 index 00000000..4b1a5acc --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +[![MEAN.JS Logo](http://meanjs.org/img/logo-small.png)](http://meanjs.org/) + +[![Build Status](https://travis-ci.org/meanjs/mean.svg?branch=master)](https://travis-ci.org/meanjs/mean) +[![Dependencies Status](https://david-dm.org/meanjs/mean.svg)](https://david-dm.org/meanjs/mean) +[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/meanjs/mean?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components. + +## Before You Begin +Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application: +* MongoDB - Go through [MongoDB Official Website](http://mongodb.org/) and proceed to their [Official Manual](http://docs.mongodb.org/manual/), which should help you understand NoSQL and MongoDB better. +* Express - The best way to understand express is through its [Official Website](http://expressjs.com/), which has a [Getting Started](http://expressjs.com/starter/installing.html) guide, as well as an [ExpressJS Guide](http://expressjs.com/guide/error-handling.html) guide for general express topics. You can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources. +* AngularJS - Angular's [Official Website](http://angularjs.org/) is a great starting point. You can also use [Thinkster Popular Guide](http://www.thinkster.io/), and the [Egghead Videos](https://egghead.io/). +* Node.js - Start by going through [Node.js Official Website](http://nodejs.org/) and this [StackOverflow Thread](http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js), which should get you going with the Node.js platform in no time. + + +## Prerequisites +Make sure you have installed all of the following prerequisites on your development machine: +* Node.js - [Download & Install Node.js](http://www.nodejs.org/download/) and the npm package manager. If you encounter any problems, you can also use this [GitHub Gist](https://gist.github.com/isaacs/579814) to install Node.js. +* MongoDB - [Download & Install MongoDB](http://www.mongodb.org/downloads), and make sure it's running on the default port (27017). +* Bower - You're going to use the [Bower Package Manager](http://bower.io/) to manage your front-end packages. Make sure you've installed Node.js and npm first, then install bower globally using npm: + +```bash +$ npm install -g bower +``` + +* Grunt - You're going to use the [Grunt Task Runner](http://gruntjs.com/) to automate your development process. Make sure you've installed Node.js and npm first, then install grunt globally using npm: + +```bash +$ npm install -g grunt-cli +``` + +## Downloading MEAN.JS +There are several ways you can get the MEAN.JS boilerplate: + +### Yo Generator +The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html), which generates the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles. + +### Cloning The GitHub Repository +You can also use Git to directly clone the MEAN.JS repository: +```bash +$ git clone https://github.com/meanjs/mean.git meanjs +``` +This will clone the latest version of the MEAN.JS repository to a **meanjs** folder. + +### Downloading The Repository Zip File +Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on GitHub](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: +```bash +$ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip +``` +Don't forget to rename **mean-master** after your project name. + +## Quick Install +Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop your MEAN application. + +The first thing you should do is install the Node.js dependencies. The boilerplate comes pre-bundled with a package.json file that contains the list of modules you need to start your application. To learn more about the modules installed visit the NPM & Package.json section. + +To install Node.js dependencies you're going to use npm again. In the application folder run this in the command-line: + +```bash +$ npm install +``` + +This command does a few things: +* First it will install the dependencies needed for the application to run. +* If you're running in a development environment, it will then also install development dependencies needed for testing and running your application. +* Finally, when the install process is over, npm will initiate a bower install command to install all the front-end modules needed for the application. + +## Running Your Application +After the install process is over, you'll be able to run your application using Grunt. Just run grunt default task: + +```bash +$ grunt +``` + +Your application should run on port 3000, so in your browser just go to [http://localhost:3000](http://localhost:3000) + +That's it! Your application should be running. To proceed with your development, check the other sections in this documentation. +If you encounter any problems, try the Troubleshooting section. + +## Testing Your Application +You can run the full test suite included with MEAN.JS with the test task: + +``` +$ grunt test +``` + +This will run both the server-side tests (located in the app/tests/ directory) and the client-side tests (located in the public/modules/*/tests/). + +To execute only the server tests, run the test:server task: + +``` +$ grunt test:server +``` + +And to run only the client tests, run the test:client task: + +``` +$ grunt test:client +``` + +## Development and deployment With Docker + +* Install [Docker](http://www.docker.com/) +* Install [Fig](https://github.com/orchardup/fig) + +* Local development and testing with fig: +```bash +$ fig up +``` + +* Local development and testing with just Docker: +```bash +$ docker build -t mean . +$ docker run -p 27017:27017 -d --name db mongo +$ docker run -p 3000:3000 --link db:db_1 mean +$ +``` + +* To enable live reload, forward port 35729 and mount /app and /public as volumes: +```bash +$ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean/public:/home/mean/public -v /Users/mdl/workspace/mean-stack/mean/app:/home/mean/app --link db:db_1 mean +``` + +## Running in a secure environment +To run your application in a secure manner you'll need to use OpenSSL and generate a set of self-signed certificates. Unix-based users can use the following command: +```bash +$ sh ./scripts/generate-ssl-certs.sh +``` +Windows users can follow instructions found [here](http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority). +After you've generated the key and certificate, place them in the *config/sslcerts* folder. + +## Getting Started With MEAN.JS +You have your application running, but there is a lot of stuff to understand. We recommend you go over the [Official Documentation](http://meanjs.org/docs.html). +In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development process. We tried covering as many aspects as possible, and will keep it updated by your request. You can also help us develop and improve the documentation by checking out the *gh-pages* branch of this repository. + +## Community +* Use the [Official Website](http://meanjs.org) to learn about changes and the roadmap. +* Join #meanjs on freenode. +* Discuss it in the new [Google Group](https://groups.google.com/d/forum/meanjs) +* Ping us on [Twitter](http://twitter.com/meanjsorg) and [Facebook](http://facebook.com/meanjs) + +## Live Example +Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com). + +## Credits +Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) +The MEAN name was coined by [Valeri Karpov](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and) + +## License +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/controllers/articles.server.controller.js b/app/controllers/articles.server.controller.js new file mode 100755 index 00000000..f5b4d27f --- /dev/null +++ b/app/controllers/articles.server.controller.js @@ -0,0 +1,120 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + errorHandler = require('./errors.server.controller'), + Article = mongoose.model('Article'), + _ = require('lodash'); + +/** + * Create a article + */ +exports.create = function(req, res) { + var article = new Article(req.body); + article.user = req.user; + + article.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + res.json(article); + } + }); +}; + +/** + * Show the current article + */ +exports.read = function(req, res) { + res.json(req.article); +}; + +/** + * Update a article + */ +exports.update = function(req, res) { + var article = req.article; + + article = _.extend(article, req.body); + + article.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + res.json(article); + } + }); +}; + +/** + * Delete an article + */ +exports.delete = function(req, res) { + var article = req.article; + + article.remove(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + res.json(article); + } + }); +}; + +/** + * List of Articles + */ +exports.list = function(req, res) { + Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + res.json(articles); + } + }); +}; + +/** + * Article middleware + */ +exports.articleByID = function(req, res, next, id) { + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).send({ + message: 'Article is invalid' + }); + } + + Article.findById(id).populate('user', 'displayName').exec(function(err, article) { + if (err) return next(err); + if (!article) { + return res.status(404).send({ + message: 'Article not found' + }); + } + req.article = article; + next(); + }); +}; + +/** + * Article authorization middleware + */ +exports.hasAuthorization = function(req, res, next) { + if (req.article.user.id !== req.user.id) { + return res.status(403).send({ + message: 'User is not authorized' + }); + } + next(); +}; diff --git a/app/controllers/core.server.controller.js b/app/controllers/core.server.controller.js new file mode 100755 index 00000000..5dfdd5e4 --- /dev/null +++ b/app/controllers/core.server.controller.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * Module dependencies. + */ +exports.index = function(req, res) { + res.render('index', { + user: req.user || null, + request: req + }); +}; diff --git a/app/controllers/errors.server.controller.js b/app/controllers/errors.server.controller.js new file mode 100755 index 00000000..5944d786 --- /dev/null +++ b/app/controllers/errors.server.controller.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * Get unique error field name + */ +var getUniqueErrorMessage = function(err) { + var output; + + try { + var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1')); + output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists'; + + } catch (ex) { + output = 'Unique field already exists'; + } + + return output; +}; + +/** + * Get the error message from error object + */ +exports.getErrorMessage = function(err) { + var message = ''; + + if (err.code) { + switch (err.code) { + case 11000: + case 11001: + message = getUniqueErrorMessage(err); + break; + default: + message = 'Something went wrong'; + } + } else { + for (var errName in err.errors) { + if (err.errors[errName].message) message = err.errors[errName].message; + } + } + + return message; +}; diff --git a/app/controllers/forms.server.controller.js b/app/controllers/forms.server.controller.js new file mode 100644 index 00000000..0830d063 --- /dev/null +++ b/app/controllers/forms.server.controller.js @@ -0,0 +1,254 @@ + + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + errorHandler = require('./errors.server.controller'), + Form = mongoose.model('Form'), + FormSubmission = mongoose.model('FormSubmission'), + pdfFiller = require( 'pdffiller' ), + PDFParser = require('pdf2json/pdfparser'), + config = require('../../config/config'), + fs = require('fs-extra'), + async = require('async'), + _ = require('lodash'); + +/** + * Create a new form manually + */ +exports.create = function(req, res) { + var form = new Form(req.body); + form.admin = req.user; + + form.save(function(err) { + + if (err) { + console.log(err); + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + + return res.json(form); + } + }); +}; + +/** + * Upload PDF + */ +exports.uploadPDF = function(req, res) { + var parser = new PDFParser(), + pdfFile = req.files.file; + + console.log(pdfFile); + + var form = Form.findById(req.body.form._id); + console.log(req.files); + + if (req.files) { + + if (pdfFile.size === 0) { + return next(new Error('Hey, first would you select a file?')); + } + fs.exists(pdfFile.path, function(exists) { + if(exists) { + console.log("UPLOADING FILE \N\N"); + // res.end('Got your file!'); + // next() + return res.status(200); + } else { + // res.end('DID NOT get your file!'); + // next() + return res.status(400); + } + }); + } + + // next(new Error('FILE NOT UPLOADED')); + + return res.status(400); + // res.json(pdfFile); +}; + +/** + * Show the current form + */ +exports.read = function(req, res) { + // console.log(req.form); + res.json(req.form); +}; + +/** + * Submit a form entry + */ +exports.createSubmission = function(req, res) { + + var submission = new FormSubmission(), + form = req.form, fdfData; + + submission.form = form; + submission.admin = req.user; + submission.form_fields = req.body.form_fields; + submission.title = req.body.title; + submission.timeElapsed = req.body.timeElapsed; + + if (form.isGenerated){ + fdfData = form.convertToJSON(); + } else { + try { + fdfData = pdfFiller.mapForm2PDF(form.convertToJSON(), form.pdfFieldMap); + } catch(err){ + throw new Error(err.message); + } + } + + submission.fdfData = fdfData; + + + + //Create new file + pdfFiller.fillForm( form.pdf.path, config.pdfUploadPath+form.title+"/"+form.title+"_"+Date.now()+"_submission.pdf", fdfData, function() { + console.log("\n\n\n fdfData"); + console.log(fdfData); + }); + + + // submission.ipAddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + + submission.save(function(err){ + if (err) { + console.error(err); + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + return res.status(200); + } + }); +}; + + +/** + * Get List of Submissions for a given Template Form + */ +exports.listSubmissions = function(req, res) { + var _form = req.form; + + FormSubmission.find({ form: req.form }).exec(function(err, submissions) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + return res.json(submissions); + } + }); +}; + + +/** + * Update a form + */ +exports.update = function(req, res) { + + var form = req.form; + form = _.extend(form, req.body); + form.admin = req.user; + + form.save(function(err) { + if (err) { + console.log(err); + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + console.log('updated form'); + return res.json(form); + } + }); +}; + +/** + * Delete a form + */ +exports.delete = function(req, res) { + var form = req.form; + + form.remove(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + return res.status(200); + // res.json(form); + } + }); +}; + +/** + * Get List of Template Forms + */ +exports.list = function(req, res) { + Form.find({ type: 'template' }).sort('-created').populate('admin').exec(function(err, forms) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + console.log(forms); + return res.json(forms); + } + }); +}; + + +/** + * Form middleware + */ +exports.formByID = function(req, res, next, id) { + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).send({ + message: 'Form is invalid' + }); + } + + Form.findById(id).populate('admin').exec(function(err, form) { + if (err) return next(err); + if (!form) { + return res.status(404).send({ + message: 'Form not found' + }); + } + + //Remove sensitive information from User object + form.admin.password = null; + form.admin.created = null; + form.admin.salt = null; + + req.form = form; + next(); + }); +}; + +/** + * Form authorization middleware + */ +exports.hasAuthorization = function(req, res, next) { + + var form = req.form; + + // console.log('\n\n\nreq.form:\n'); + // console.log(form); + // console.log('req.user.id: '+req.user.id); + + if (req.form.admin.id !== req.user.id) { + return res.status(403).send({ + message: 'User is not authorized' + }); + } + next(); +}; diff --git a/app/controllers/users.server.controller.js b/app/controllers/users.server.controller.js new file mode 100755 index 00000000..06ef00ea --- /dev/null +++ b/app/controllers/users.server.controller.js @@ -0,0 +1,16 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'); + +/** + * Extend user's controller + */ +module.exports = _.extend( + require('./users/users.authentication.server.controller'), + require('./users/users.authorization.server.controller'), + require('./users/users.password.server.controller'), + require('./users/users.profile.server.controller') +); diff --git a/app/controllers/users/users.authentication.server.controller.js b/app/controllers/users/users.authentication.server.controller.js new file mode 100755 index 00000000..d34642b5 --- /dev/null +++ b/app/controllers/users/users.authentication.server.controller.js @@ -0,0 +1,206 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors.server.controller'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'); + +/** + * Signup + */ +exports.signup = function(req, res) { + // For security measurement we remove the roles from the req.body object + delete req.body.roles; + + // Init Variables + var user = new User(req.body); + var message = null; + + // Add missing user fields + user.provider = 'local'; + user.displayName = user.firstName + ' ' + user.lastName; + + // Then save the user + user.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + // Remove sensitive data before login + user.password = undefined; + user.salt = undefined; + + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + }); +}; + +/** + * Signin after passport authentication + */ +exports.signin = function(req, res, next) { + passport.authenticate('local', function(err, user, info) { + if (err || !user) { + res.status(400).send(info); + } else { + // Remove sensitive data before login + user.password = undefined; + user.salt = undefined; + + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + })(req, res, next); +}; + +/** + * Signout + */ +exports.signout = function(req, res) { + req.logout(); + res.redirect('/'); +}; + +/** + * OAuth callback + */ +exports.oauthCallback = function(strategy) { + return function(req, res, next) { + passport.authenticate(strategy, function(err, user, redirectURL) { + if (err || !user) { + return res.redirect('/#!/signin'); + } + req.login(user, function(err) { + if (err) { + return res.redirect('/#!/signin'); + } + + return res.redirect(redirectURL || '/'); + }); + })(req, res, next); + }; +}; + +/** + * Helper function to save or update a OAuth user profile + */ +exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { + if (!req.user) { + // Define a search query fields + var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; + var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; + + // Define main provider search query + var mainProviderSearchQuery = {}; + mainProviderSearchQuery.provider = providerUserProfile.provider; + mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define additional provider search query + var additionalProviderSearchQuery = {}; + additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define a search query to find existing user with current provider profile + var searchQuery = { + $or: [mainProviderSearchQuery, additionalProviderSearchQuery] + }; + + User.findOne(searchQuery, function(err, user) { + if (err) { + return done(err); + } else { + if (!user) { + var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); + + User.findUniqueUsername(possibleUsername, null, function(availableUsername) { + user = new User({ + firstName: providerUserProfile.firstName, + lastName: providerUserProfile.lastName, + username: availableUsername, + displayName: providerUserProfile.displayName, + email: providerUserProfile.email, + provider: providerUserProfile.provider, + providerData: providerUserProfile.providerData + }); + + // And save the user + user.save(function(err) { + return done(err, user); + }); + }); + } else { + return done(err, user); + } + } + }); + } else { + // User is already logged in, join the provider data to the existing user + var user = req.user; + + // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured + if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { + // Add the provider data to the additional provider data field + if (!user.additionalProvidersData) user.additionalProvidersData = {}; + user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + + // And save the user + user.save(function(err) { + return done(err, user, '/#!/settings/accounts'); + }); + } else { + return done(new Error('User is already connected using this provider'), user); + } + } +}; + +/** + * Remove OAuth provider + */ +exports.removeOAuthProvider = function(req, res, next) { + var user = req.user; + var provider = req.param('provider'); + + if (user && provider) { + // Delete the additional provider + if (user.additionalProvidersData[provider]) { + delete user.additionalProvidersData[provider]; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + } + + user.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + }); + } +}; diff --git a/app/controllers/users/users.authorization.server.controller.js b/app/controllers/users/users.authorization.server.controller.js new file mode 100755 index 00000000..0a615b34 --- /dev/null +++ b/app/controllers/users/users.authorization.server.controller.js @@ -0,0 +1,53 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + mongoose = require('mongoose'), + User = mongoose.model('User'); + +/** + * User middleware + */ +exports.userByID = function(req, res, next, id) { + User.findById(id).exec(function(err, user) { + if (err) return next(err); + if (!user) return next(new Error('Failed to load User ' + id)); + req.profile = user; + next(); + }); +}; + +/** + * Require login routing middleware + */ +exports.requiresLogin = function(req, res, next) { + + if (!req.isAuthenticated()) { + return res.status(401).send({ + message: 'User is not logged in' + }); + } + + next(); +}; + +/** + * User authorizations routing middleware + */ +exports.hasAuthorization = function(roles) { + var _this = this; + + return function(req, res, next) { + _this.requiresLogin(req, res, function() { + if (_.intersection(req.user.roles, roles).length) { + return next(); + } else { + return res.status(403).send({ + message: 'User is not authorized' + }); + } + }); + }; +}; diff --git a/app/controllers/users/users.password.server.controller.js b/app/controllers/users/users.password.server.controller.js new file mode 100755 index 00000000..e246baed --- /dev/null +++ b/app/controllers/users/users.password.server.controller.js @@ -0,0 +1,249 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors.server.controller'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'), + config = require('../../../config/config'), + nodemailer = require('nodemailer'), + async = require('async'), + crypto = require('crypto'); + +var smtpTransport = nodemailer.createTransport(config.mailer.options); + +/** + * Forgot for reset password (forgot POST) + */ +exports.forgot = function(req, res, next) { + async.waterfall([ + // Generate random token + function(done) { + crypto.randomBytes(20, function(err, buffer) { + var token = buffer.toString('hex'); + done(err, token); + }); + }, + // Lookup user by username + function(token, done) { + if (req.body.username) { + User.findOne({ + username: req.body.username + }, '-salt -password', function(err, user) { + if (!user) { + return res.status(400).send({ + message: 'No account with that username has been found' + }); + } else if (user.provider !== 'local') { + return res.status(400).send({ + message: 'It seems like you signed up using your ' + user.provider + ' account' + }); + } else { + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + + user.save(function(err) { + done(err, token, user); + }); + } + }); + } else { + return res.status(400).send({ + message: 'Username field must not be blank' + }); + } + }, + function(token, user, done) { + res.render('templates/reset-password-email', { + name: user.displayName, + appName: config.app.title, + url: 'http://' + req.headers.host + '/auth/reset/' + token + }, function(err, emailHTML) { + done(err, emailHTML, user); + }); + }, + // If valid email, send reset email using service + function(emailHTML, user, done) { + var mailOptions = { + to: user.email, + from: config.mailer.from, + subject: 'Password Reset', + html: emailHTML + }; + smtpTransport.sendMail(mailOptions, function(err) { + if (!err) { + res.send({ + message: 'An email has been sent to ' + user.email + ' with further instructions.' + }); + } else { + return res.status(400).send({ + message: 'Failure sending email' + }); + } + + done(err); + }); + } + ], function(err) { + if (err) return next(err); + }); +}; + +/** + * Reset password GET from email token + */ +exports.validateResetToken = function(req, res) { + User.findOne({ + resetPasswordToken: req.params.token, + resetPasswordExpires: { + $gt: Date.now() + } + }, function(err, user) { + if (!user) { + return res.redirect('/#!/password/reset/invalid'); + } + + res.redirect('/#!/password/reset/' + req.params.token); + }); +}; + +/** + * Reset password POST from email token + */ +exports.reset = function(req, res, next) { + // Init Variables + var passwordDetails = req.body; + + async.waterfall([ + + function(done) { + User.findOne({ + resetPasswordToken: req.params.token, + resetPasswordExpires: { + $gt: Date.now() + } + }, function(err, user) { + if (!err && user) { + if (passwordDetails.newPassword === passwordDetails.verifyPassword) { + user.password = passwordDetails.newPassword; + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + + user.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + // Return authenticated user + res.json(user); + + done(err, user); + } + }); + } + }); + } else { + return res.status(400).send({ + message: 'Passwords do not match' + }); + } + } else { + return res.status(400).send({ + message: 'Password reset token is invalid or has expired.' + }); + } + }); + }, + function(user, done) { + res.render('templates/reset-password-confirm-email', { + name: user.displayName, + appName: config.app.title + }, function(err, emailHTML) { + done(err, emailHTML, user); + }); + }, + // If valid email, send reset email using service + function(emailHTML, user, done) { + var mailOptions = { + to: user.email, + from: config.mailer.from, + subject: 'Your password has been changed', + html: emailHTML + }; + + smtpTransport.sendMail(mailOptions, function(err) { + done(err, 'done'); + }); + } + ], function(err) { + if (err) return next(err); + }); +}; + +/** + * Change Password + */ +exports.changePassword = function(req, res) { + // Init Variables + var passwordDetails = req.body; + + if (req.user) { + if (passwordDetails.newPassword) { + User.findById(req.user.id, function(err, user) { + if (!err && user) { + if (user.authenticate(passwordDetails.currentPassword)) { + if (passwordDetails.newPassword === passwordDetails.verifyPassword) { + user.password = passwordDetails.newPassword; + + user.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + res.send({ + message: 'Password changed successfully' + }); + } + }); + } + }); + } else { + res.status(400).send({ + message: 'Passwords do not match' + }); + } + } else { + res.status(400).send({ + message: 'Current password is incorrect' + }); + } + } else { + res.status(400).send({ + message: 'User is not found' + }); + } + }); + } else { + res.status(400).send({ + message: 'Please provide a new password' + }); + } + } else { + res.status(400).send({ + message: 'User is not signed in' + }); + } +}; diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js new file mode 100755 index 00000000..8e438f7c --- /dev/null +++ b/app/controllers/users/users.profile.server.controller.js @@ -0,0 +1,56 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors.server.controller.js'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'); + +/** + * Update user details + */ +exports.update = function(req, res) { + // Init Variables + var user = req.user; + var message = null; + + // For security measurement we remove the roles from the req.body object + delete req.body.roles; + + if (user) { + // Merge existing user + user = _.extend(user, req.body); + user.updated = Date.now(); + user.displayName = user.firstName + ' ' + user.lastName; + + user.save(function(err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + }); + } else { + res.status(400).send({ + message: 'User is not signed in' + }); + } +}; + +/** + * Send User + */ +exports.me = function(req, res) { + res.json(req.user || null); +}; diff --git a/app/models/article.server.model.js b/app/models/article.server.model.js new file mode 100755 index 00000000..f2b89db8 --- /dev/null +++ b/app/models/article.server.model.js @@ -0,0 +1,34 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +/** + * Article Schema + */ +var ArticleSchema = new Schema({ + created: { + type: Date, + default: Date.now + }, + title: { + type: String, + default: '', + trim: true, + required: 'Title cannot be blank' + }, + content: { + type: String, + default: '', + trim: true + }, + user: { + type: Schema.ObjectId, + ref: 'User' + } +}); + +mongoose.model('Article', ArticleSchema); diff --git a/app/models/form.server.model.js b/app/models/form.server.model.js new file mode 100644 index 00000000..e27e16bb --- /dev/null +++ b/app/models/form.server.model.js @@ -0,0 +1,182 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + FieldSchema = require('./form_field.server.model.js'), + Schema = mongoose.Schema, + pdfFiller = require('pdfFiller'), + _ = require('lodash'), + config = require('../../config/config'), + path = require('path'), + fs = require('fs-extra'); + +var Field = mongoose.model('Field', FieldSchema); + + +/** + * Form Schema + */ +var FormSchema = new Schema({ + created: { + type: Date, + default: Date.now + }, + // type: { + // type: String, + // default: 'template', + // enum: ['submission', 'template'] + // }, + title: { + type: String, + default: '', + trim: true, + unique: true, + required: 'Title cannot be blank' + }, + description: { + type: String, + default: '', + }, + form_fields: [Schema.Types.Mixed], + + submission: [{ + type: Schema.Types.ObjectId, + ref: 'FormSubmission' + }], + + admin: { + type: Schema.Types.ObjectId, + ref: 'User' + }, + + pdf: { + type: Schema.Types.Mixed + }, + pdfFieldMap: { + type: Schema.Types.Mixed + }, + isGenerated: { + type: Boolean, + default: false, + }, + autofillPDFs: { + type: Boolean, + default: false, + }, +}); + +FormSchema.pre('save', function (next) { + // console.log(this.pdf); + // debugger; + + //Move PDF to permanent location after first save + if(this.pdf){ + if(this.pdf.modified){ + + var new_filename = this.pdf.title.trim()+'_template.pdf'; + + // TODO: DAVID - need to remove dependence on relative paths + var newDestination = path.join(config.pdfUploadPath+this.title+"/", this.pdf.title.trim()), + stat = null; + + try { + stat = fs.statSync(newDestination); + } catch (err) { + fs.mkdirSync(newDestination); + } + if (stat && !stat.isDirectory()) { + console.log('Directory cannot be created'); + next( new Error('Directory cannot be created because an inode of a different type exists at "' + config.pdfUploadPath + '"') ); + } + + console.log('about to move PDF'); + + + fs.move(this.pdf.path, path.join(newDestination, new_filename), function (err) { + if (err) { + console.error(err); + next( new Error(err.message) ); + } + console.log('PDF file successfully moved'); + + this.pdf.path = path.join(newDestination, new_filename); + this.pdf.name = new_filename; + + next(); + }); + + } + }else { + next(); + } +}); + +// FormSchema.pre('save', function (next) { +// //Autogenerate FORM from PDF +// if(this.isGenerated && this.pdf && this.autofillPDFs){ +// this.autofillPDFs = false; +// var _pdfConvMap = { +// 'Text': 'textfield', +// 'Button': 'checkbox' +// }; + +// var that = this; +// console.log('autogenerating form'); + +// try { +// pdfFiller.generateFieldJson(this.pdf.path, function(_form_fields){ + +// _form_fields.forEach(function(field){ +// if(_pdfConvMap[ field.fieldType+'' ]){ +// field.fieldType = _pdfConvMap[ field.fieldType+'' ]; +// } +// field.created = Date.now(); +// field.fieldValue = ''; +// field.required = true; +// field.disabled = false; + +// // field = new Field(field); +// // field.save() +// }); + +// // console.log('NEW FORM_FIELDS: '); +// // console.log(_form_fields); + +// // console.log('\n\nOLD FORM_FIELDS: '); +// // console.log(that.form_fields); + +// that.form_fields = _form_fields; +// next(); +// }); +// } catch(err){ +// next( new Error(err.message) ); +// } + +// } + +// //Throw error if we encounter form with invalid type +// next(); + +// }); + +FormSchema.methods.convertToJSON = function (cb) { + var _keys = _.pluck(this.form_fields, 'title'), + _values = _.pluck(this.form_fields, 'fieldValue'); + + _values.forEach(function(val){ + if(val === true){ + val = 'Yes'; + }else if(val === false) { + val = 'Off'; + } + }); + + var jsonObj = _.zipObject(_keys, _values); + + return jsonObj; +}; + + +mongoose.model('Form', FormSchema); diff --git a/app/models/form_field.server.model.js b/app/models/form_field.server.model.js new file mode 100644 index 00000000..d3725aa6 --- /dev/null +++ b/app/models/form_field.server.model.js @@ -0,0 +1,69 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +// questionType Validation +function validateFormFieldType(value) { + if (!value || typeof myVar !== 'string' ) { return false; } + + var validTypes = [ + 'textfield', + 'email', + 'url', + 'textarea', + 'checkbox', + 'date', + 'dropdown', + 'hidden', + 'password', + 'radio' + ]; + if (validTypes.indexOf(value) > -1) { + return true; + } + return false; +} + + +/** + * Question Schema + */ +var FormFieldSchema = new Schema({ + created: { + type: Date, + default: Date.now + }, + title: { + type: String, + default: '', + trim: true, + required: 'Title cannot be blank' + }, + description: { + type: String, + default: '', + }, + required: { + type: Boolean, + default: false, + }, + disabled: { + type: Boolean, + default: false, + }, + fieldType: { + type: String, + required: 'Field type cannot be blank', + validate: [validateFormFieldType, 'Invalid field type'] + }, + fieldValue: Schema.Types.Mixed + +}); + +module.exports = FormFieldSchema; + +// mongoose.model('Field', FormFieldSchema); \ No newline at end of file diff --git a/app/models/form_submission.server.model.js b/app/models/form_submission.server.model.js new file mode 100644 index 00000000..777d7c8b --- /dev/null +++ b/app/models/form_submission.server.model.js @@ -0,0 +1,114 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + pdfFiller = require('pdfFiller'), + satelize = require('satelize'), + _ = require('lodash'), + config = require('../../config/config'), + path = require('path'), + Form = mongoose.model('Form'), + fs = require('fs-extra'); + +/** + * Form Submission Schema + */ +var FormSubmissionSchema = new Schema({ + title: { + type: String, + required: true, + }, + created: { + type: Date, + default: Date.now + }, + + admin: { + type: Schema.Types.ObjectId, + ref: 'User', + }, + form_fields: [Schema.Types.Mixed], + form: { + type: Schema.Types.ObjectId, + ref: 'Form', + required: true + }, + + ipAddr: { + type: String, + required: false + }, + geoLocation: { + type: Schema.Types.Mixed, + }, + + pdfFilePath: { + type: Schema.Types.Mixed, + }, + fdfData: { + type: Schema.Types.Mixed, + }, + + timeElapsed: { //time (in seconds) it took for user to submit form + type: Number, + }, + +}); + + +FormSubmissionSchema.pre('save', function (next){ + if(this.ipAddr){ + if(this.ipAddr.modified){ + satelize.satelize({ip: this.ipAddr}, function(err, geoData){ + if (err) next( new Error(err.message) ); + + this.geoLocation = JSON.parse(geoData); + next(); + }); + } + } + console.log('ipAddr check'); + next(); +}); + +FormSubmissionSchema.pre('save', function (next) { + // debugger; + var fdfData, dest_filename, dest_path; + var that = this; + + Form.findById(that.form, function(err, _form){ + if(err) next( new Error(err.mesasge) ); + + this.title = _form.title; + console.log(_form); + //Create filled-out PDF, if there is a pdf template + if(_form.autofillPDFs){ + + dest_filename = this.title.trim()+'_submission_'+Date.now()+'.pdf'; + dest_path = path.join(config.pdfUploadPath, this.title.trim(), dest_filename); + + this.pdfFilePath = dest_path; + + console.log('autofillPDFs check'); + + pdfFiller.fillForm(_form.pdf.path, dest_path, this.fdfData, function(err){ + + if(err) next( new Error(err.message) ); + + console.log('Field data from Form: '+this.title.trim()+' outputed to new PDF: '+dest_path); + next(); + }); + } else { + + next(); + } + + }); + + +}); + +mongoose.model('FormSubmission', FormSubmissionSchema); \ No newline at end of file diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js new file mode 100755 index 00000000..c59c9da8 --- /dev/null +++ b/app/models/user.server.model.js @@ -0,0 +1,147 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + crypto = require('crypto'); + +/** + * A Validation function for local strategy properties + */ +var validateLocalStrategyProperty = function(property) { + return ((this.provider !== 'local' && !this.updated) || property.length); +}; + +/** + * A Validation function for local strategy password + */ +var validateLocalStrategyPassword = function(password) { + return (this.provider !== 'local' || (password && password.length > 6)); +}; + +/** + * User Schema + */ +var UserSchema = new Schema({ + firstName: { + type: String, + trim: true, + default: '', + validate: [validateLocalStrategyProperty, 'Please fill in your first name'] + }, + lastName: { + type: String, + trim: true, + default: '', + validate: [validateLocalStrategyProperty, 'Please fill in your last name'] + }, + displayName: { + type: String, + trim: true + }, + email: { + type: String, + trim: true, + default: '', + validate: [validateLocalStrategyProperty, 'Please fill in your email'], + match: [/.+\@.+\..+/, 'Please fill a valid email address'] + }, + username: { + type: String, + unique: 'Username already exists', + required: 'Please fill in a username', + trim: true + }, + password: { + type: String, + default: '', + validate: [validateLocalStrategyPassword, 'Password should be longer'] + }, + salt: { + type: String + }, + provider: { + type: String, + required: 'Provider is required' + }, + providerData: {}, + additionalProvidersData: {}, + roles: { + type: [{ + type: String, + enum: ['user', 'admin'] + }], + default: ['user'] + }, + updated: { + type: Date + }, + created: { + type: Date, + default: Date.now + }, + /* For reset password */ + resetPasswordToken: { + type: String + }, + resetPasswordExpires: { + type: Date + }, + token: String +}); + +/** + * Hook a pre save method to hash the password + */ +UserSchema.pre('save', function(next) { + if (this.password && this.password.length > 6) { + this.salt = crypto.randomBytes(16).toString('base64'); + this.password = this.hashPassword(this.password); + } + + next(); +}); + +/** + * Create instance method for hashing a password + */ +UserSchema.methods.hashPassword = function(password) { + if (this.salt && password) { + return crypto.pbkdf2Sync(password, new Buffer(this.salt, 'base64'), 10000, 64).toString('base64'); + } else { + return password; + } +}; + +/** + * Create instance method for authenticating user + */ +UserSchema.methods.authenticate = function(password) { + return this.password === this.hashPassword(password); +}; + +/** + * Find possible not used username + */ +UserSchema.statics.findUniqueUsername = function(username, suffix, callback) { + var _this = this; + var possibleUsername = username + (suffix || ''); + + _this.findOne({ + username: possibleUsername + }, function(err, user) { + if (!err) { + if (!user) { + callback(possibleUsername); + } else { + return _this.findUniqueUsername(username, (suffix || 0) + 1, callback); + } + } else { + callback(null); + } + }); +}; + +mongoose.model('User', UserSchema); diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js new file mode 100755 index 00000000..9a93d059 --- /dev/null +++ b/app/routes/articles.server.routes.js @@ -0,0 +1,22 @@ +'use strict'; + +/** + * Module dependencies. + */ +var users = require('../../app/controllers/users.server.controller'), + articles = require('../../app/controllers/articles.server.controller'); + +module.exports = function(app) { + // Article Routes + app.route('/articles') + .get(articles.list) + .post(users.requiresLogin, articles.create); + + app.route('/articles/:articleId') + .get(articles.read) + .put(users.requiresLogin, articles.hasAuthorization, articles.update) + .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); + + // Finish by binding the article middleware + app.param('articleId', articles.articleByID); +}; diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js new file mode 100755 index 00000000..71388226 --- /dev/null +++ b/app/routes/core.server.routes.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function(app) { + // Root routing + var core = require('../../app/controllers/core.server.controller'); + app.route('/').get(core.index); +}; diff --git a/app/routes/forms.server.routes.js b/app/routes/forms.server.routes.js new file mode 100644 index 00000000..c271c943 --- /dev/null +++ b/app/routes/forms.server.routes.js @@ -0,0 +1,26 @@ +'use strict'; + +/** + * Module dependencies. + */ +var users = require('../../app/controllers/users.server.controller'), + forms = require('../../app/controllers/forms.server.controller'); + +module.exports = function(app) { + // Form Routes + app.route('/upload/pdf') + .post(forms.uploadPDF); + + app.route('/forms') + .get(forms.list) + .post(users.requiresLogin, forms.create); + + app.route('/forms/:formId') + .get(forms.read) + .post(forms.createSubmission) + .put(users.requiresLogin, forms.hasAuthorization, forms.update) + .delete(users.requiresLogin, forms.delete); + + // Finish by binding the form middleware + app.param('formId', forms.formByID); +}; diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js new file mode 100755 index 00000000..a3005346 --- /dev/null +++ b/app/routes/users.server.routes.js @@ -0,0 +1,57 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'); + +module.exports = function(app) { + // User Routes + var users = require('../../app/controllers/users.server.controller'); + + // Setting up the users profile api + app.route('/users/me').get(users.me); + app.route('/users').put(users.update); + app.route('/users/accounts').delete(users.removeOAuthProvider); + + // Setting up the users password api + app.route('/users/password').post(users.changePassword); + app.route('/auth/forgot').post(users.forgot); + app.route('/auth/reset/:token').get(users.validateResetToken); + app.route('/auth/reset/:token').post(users.reset); + + // Setting up the users authentication api + app.route('/auth/signup').post(users.signup); + app.route('/auth/signin').post(users.signin); + app.route('/auth/signout').get(users.signout); + + // Setting the facebook oauth routes + app.route('/auth/facebook').get(passport.authenticate('facebook', { + scope: ['email'] + })); + app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); + + // Setting the twitter oauth routes + app.route('/auth/twitter').get(passport.authenticate('twitter')); + app.route('/auth/twitter/callback').get(users.oauthCallback('twitter')); + + // Setting the google oauth routes + app.route('/auth/google').get(passport.authenticate('google', { + scope: [ + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/userinfo.email' + ] + })); + app.route('/auth/google/callback').get(users.oauthCallback('google')); + + // Setting the linkedin oauth routes + app.route('/auth/linkedin').get(passport.authenticate('linkedin')); + app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin')); + + // Setting the github oauth routes + app.route('/auth/github').get(passport.authenticate('github')); + app.route('/auth/github/callback').get(users.oauthCallback('github')); + + // Finish by binding the user middleware + app.param('userId', users.userByID); +}; diff --git a/app/tests/article.server.model.test.js b/app/tests/article.server.model.test.js new file mode 100755 index 00000000..b4b76b8a --- /dev/null +++ b/app/tests/article.server.model.test.js @@ -0,0 +1,64 @@ +'use strict'; + +/** + * Module dependencies. + */ +var should = require('should'), + mongoose = require('mongoose'), + User = mongoose.model('User'), + Article = mongoose.model('Article'); + +/** + * Globals + */ +var user, article; + +/** + * Unit tests + */ +describe('Article Model Unit Tests:', function() { + beforeEach(function(done) { + user = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + username: 'username', + password: 'password' + }); + + user.save(function() { + article = new Article({ + title: 'Article Title', + content: 'Article Content', + user: user + }); + + done(); + }); + }); + + describe('Method Save', function() { + it('should be able to save without problems', function(done) { + return article.save(function(err) { + should.not.exist(err); + done(); + }); + }); + + it('should be able to show an error when try to save without title', function(done) { + article.title = ''; + + return article.save(function(err) { + should.exist(err); + done(); + }); + }); + }); + + afterEach(function(done) { + Article.remove().exec(function() { + User.remove().exec(done); + }); + }); +}); diff --git a/app/tests/article.server.routes.test.js b/app/tests/article.server.routes.test.js new file mode 100755 index 00000000..c583c3f3 --- /dev/null +++ b/app/tests/article.server.routes.test.js @@ -0,0 +1,280 @@ +'use strict'; + +var should = require('should'), + request = require('supertest'), + app = require('../../server'), + mongoose = require('mongoose'), + User = mongoose.model('User'), + Article = mongoose.model('Article'), + agent = request.agent(app); + +/** + * Globals + */ +var credentials, user, article; + +/** + * Article routes tests + */ +describe('Article CRUD tests', function() { + beforeEach(function(done) { + // Create user credentials + credentials = { + username: 'username', + password: 'password' + }; + + // Create a new user + user = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + username: credentials.username, + password: credentials.password, + provider: 'local' + }); + + // Save a user to the test db and create new article + user.save(function() { + article = { + title: 'Article Title', + content: 'Article Content' + }; + + done(); + }); + }); + + it('should be able to save an article if logged in', function(done) { + agent.post('/auth/signin') + .send(credentials) + .expect(200) + .end(function(signinErr, signinRes) { + // Handle signin error + if (signinErr) done(signinErr); + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/articles') + .send(article) + .expect(200) + .end(function(articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) done(articleSaveErr); + + // Get a list of articles + agent.get('/articles') + .end(function(articlesGetErr, articlesGetRes) { + // Handle article save error + if (articlesGetErr) done(articlesGetErr); + + // Get articles list + var articles = articlesGetRes.body; + + // Set assertions + (articles[0].user._id).should.equal(userId); + (articles[0].title).should.match('Article Title'); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should not be able to save an article if not logged in', function(done) { + agent.post('/articles') + .send(article) + .expect(401) + .end(function(articleSaveErr, articleSaveRes) { + // Call the assertion callback + done(articleSaveErr); + }); + }); + + it('should not be able to save an article if no title is provided', function(done) { + // Invalidate title field + article.title = ''; + + agent.post('/auth/signin') + .send(credentials) + .expect(200) + .end(function(signinErr, signinRes) { + // Handle signin error + if (signinErr) done(signinErr); + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/articles') + .send(article) + .expect(400) + .end(function(articleSaveErr, articleSaveRes) { + // Set message assertion + (articleSaveRes.body.message).should.match('Title cannot be blank'); + + // Handle article save error + done(articleSaveErr); + }); + }); + }); + + it('should be able to update an article if signed in', function(done) { + agent.post('/auth/signin') + .send(credentials) + .expect(200) + .end(function(signinErr, signinRes) { + // Handle signin error + if (signinErr) done(signinErr); + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/articles') + .send(article) + .expect(200) + .end(function(articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) done(articleSaveErr); + + // Update article title + article.title = 'WHY YOU GOTTA BE SO MEAN?'; + + // Update an existing article + agent.put('/articles/' + articleSaveRes.body._id) + .send(article) + .expect(200) + .end(function(articleUpdateErr, articleUpdateRes) { + // Handle article update error + if (articleUpdateErr) done(articleUpdateErr); + + // Set assertions + (articleUpdateRes.body._id).should.equal(articleSaveRes.body._id); + (articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?'); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should be able to get a list of articles if not signed in', function(done) { + // Create new article model instance + var articleObj = new Article(article); + + // Save the article + articleObj.save(function() { + // Request articles + request(app).get('/articles') + .end(function(req, res) { + // Set assertion + res.body.should.be.an.Array.with.lengthOf(1); + + // Call the assertion callback + done(); + }); + + }); + }); + + + it('should be able to get a single article if not signed in', function(done) { + // Create new article model instance + var articleObj = new Article(article); + + // Save the article + articleObj.save(function() { + request(app).get('/articles/' + articleObj._id) + .end(function(req, res) { + // Set assertion + res.body.should.be.an.Object.with.property('title', article.title); + + // Call the assertion callback + done(); + }); + }); + }); + + it('should return proper error for single article which doesnt exist, if not signed in', function(done) { + request(app).get('/articles/test') + .end(function(req, res) { + // Set assertion + res.body.should.be.an.Object.with.property('message', 'Article is invalid'); + + // Call the assertion callback + done(); + }); + }); + + it('should be able to delete an article if signed in', function(done) { + agent.post('/auth/signin') + .send(credentials) + .expect(200) + .end(function(signinErr, signinRes) { + // Handle signin error + if (signinErr) done(signinErr); + + // Get the userId + var userId = user.id; + + // Save a new article + agent.post('/articles') + .send(article) + .expect(200) + .end(function(articleSaveErr, articleSaveRes) { + // Handle article save error + if (articleSaveErr) done(articleSaveErr); + + // Delete an existing article + agent.delete('/articles/' + articleSaveRes.body._id) + .send(article) + .expect(200) + .end(function(articleDeleteErr, articleDeleteRes) { + // Handle article error error + if (articleDeleteErr) done(articleDeleteErr); + + // Set assertions + (articleDeleteRes.body._id).should.equal(articleSaveRes.body._id); + + // Call the assertion callback + done(); + }); + }); + }); + }); + + it('should not be able to delete an article if not signed in', function(done) { + // Set article user + article.user = user; + + // Create new article model instance + var articleObj = new Article(article); + + // Save the article + articleObj.save(function() { + // Try deleting article + request(app).delete('/articles/' + articleObj._id) + .expect(401) + .end(function(articleDeleteErr, articleDeleteRes) { + // Set message assertion + (articleDeleteRes.body.message).should.match('User is not logged in'); + + // Handle article error error + done(articleDeleteErr); + }); + + }); + }); + + afterEach(function(done) { + User.remove().exec(function() { + Article.remove().exec(done); + }); + }); +}); diff --git a/app/tests/user.server.model.test.js b/app/tests/user.server.model.test.js new file mode 100755 index 00000000..e4384876 --- /dev/null +++ b/app/tests/user.server.model.test.js @@ -0,0 +1,75 @@ +'use strict'; + +/** + * Module dependencies. + */ +var should = require('should'), + mongoose = require('mongoose'), + User = mongoose.model('User'); + +/** + * Globals + */ +var user, user2; + +/** + * Unit tests + */ +describe('User Model Unit Tests:', function() { + before(function(done) { + user = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + username: 'username', + password: 'password', + provider: 'local' + }); + user2 = new User({ + firstName: 'Full', + lastName: 'Name', + displayName: 'Full Name', + email: 'test@test.com', + username: 'username', + password: 'password', + provider: 'local' + }); + + done(); + }); + + describe('Method Save', function() { + it('should begin with no users', function(done) { + User.find({}, function(err, users) { + users.should.have.length(0); + done(); + }); + }); + + it('should be able to save without problems', function(done) { + user.save(done); + }); + + it('should fail to save an existing user again', function(done) { + user.save(function() { + user2.save(function(err) { + should.exist(err); + done(); + }); + }); + }); + + it('should be able to show an error when try to save without first name', function(done) { + user.firstName = ''; + return user.save(function(err) { + should.exist(err); + done(); + }); + }); + }); + + after(function(done) { + User.remove().exec(done); + }); +}); diff --git a/app/views/404.server.view.html b/app/views/404.server.view.html new file mode 100755 index 00000000..40407617 --- /dev/null +++ b/app/views/404.server.view.html @@ -0,0 +1,8 @@ +{% extends 'layout.server.view.html' %} + +{% block content %} +

Page Not Found

+
+	{{url}} is not a valid path.
+
+{% endblock %} diff --git a/app/views/500.server.view.html b/app/views/500.server.view.html new file mode 100755 index 00000000..cc3b1478 --- /dev/null +++ b/app/views/500.server.view.html @@ -0,0 +1,8 @@ +{% extends 'layout.server.view.html' %} + +{% block content %} +

Server Error

+
+	{{error}}
+
+{% endblock %} diff --git a/app/views/index.server.view.html b/app/views/index.server.view.html new file mode 100755 index 00000000..7e60893b --- /dev/null +++ b/app/views/index.server.view.html @@ -0,0 +1,5 @@ +{% extends 'layout.server.view.html' %} + +{% block content %} +
+{% endblock %} diff --git a/app/views/layout.server.view.html b/app/views/layout.server.view.html new file mode 100755 index 00000000..81112808 --- /dev/null +++ b/app/views/layout.server.view.html @@ -0,0 +1,82 @@ + + + + + {{title}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for cssFile in cssFiles %} + + {% endfor %} + + + + + + +
+
+
+ {% block content %}{% endblock %} +
+
+ + + + + + {% for jsFile in jsFiles %} + + {% endfor %} + + {% if process.env.NODE_ENV === 'development' %} + + + {% endif %} + + + + + + diff --git a/app/views/templates/reset-password-confirm-email.server.view.html b/app/views/templates/reset-password-confirm-email.server.view.html new file mode 100755 index 00000000..bfbcb157 --- /dev/null +++ b/app/views/templates/reset-password-confirm-email.server.view.html @@ -0,0 +1,13 @@ + + + + + +

Dear {{name}},

+

+

This is a confirmation that the password for your account has just been changed

+
+
+

The {{appName}} Support Team

+ + diff --git a/app/views/templates/reset-password-email.server.view.html b/app/views/templates/reset-password-email.server.view.html new file mode 100755 index 00000000..4869dfd8 --- /dev/null +++ b/app/views/templates/reset-password-email.server.view.html @@ -0,0 +1,18 @@ + + + + + +

Dear {{name}},

+
+

+ You have requested to have your password reset for your account at {{appName}} +

+

Please visit this url to reset your password:

+

{{url}}

+ If you didn't make this request, you can ignore this email. +
+
+

The {{appName}} Support Team

+ + diff --git a/bower.json b/bower.json new file mode 100755 index 00000000..f5c986ee --- /dev/null +++ b/bower.json @@ -0,0 +1,21 @@ +{ + "name": "meanjs", + "version": "0.3.2", + "description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.", + "dependencies": { + "bootstrap": "~3", + "angular": "~1.2", + "angular-resource": "~1.2", + "angular-animate": "~1.2", + "angular-mocks": "~1.2", + "angular-bootstrap": "~0.12.0", + "angular-ui-utils": "~0.1.1", + "angular-ui-router": "~0.2.11", + "angular-strap": "~2.2.1" + }, + "resolutions": { + "angular": "^1.2.21", + "angular-resource": "~1.2", + "ng-file-upload": "~4.1.0" + } +} diff --git a/config/config.js b/config/config.js new file mode 100755 index 00000000..ef4a4c2c --- /dev/null +++ b/config/config.js @@ -0,0 +1,76 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + glob = require('glob'); + +/** + * Load app configurations + */ +module.exports = _.extend( + require('./env/all'), + require('./env/' + process.env.NODE_ENV) || {} +); + +/** + * Get files by glob patterns + */ +module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { + // For context switching + var _this = this; + + // URL paths regex + var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); + + // The output array + var output = []; + + // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob + if (_.isArray(globPatterns)) { + globPatterns.forEach(function(globPattern) { + output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); + }); + } else if (_.isString(globPatterns)) { + if (urlRegex.test(globPatterns)) { + output.push(globPatterns); + } else { + glob(globPatterns, { + sync: true + }, function(err, files) { + if (removeRoot) { + files = files.map(function(file) { + return file.replace(removeRoot, ''); + }); + } + + output = _.union(output, files); + }); + } + } + + return output; +}; + +/** + * Get the modules JavaScript files + */ +module.exports.getJavaScriptAssets = function(includeTests) { + var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/'); + + // To include tests + if (includeTests) { + output = _.union(output, this.getGlobbedFiles(this.assets.tests)); + } + + return output; +}; + +/** + * Get the modules CSS files + */ +module.exports.getCSSAssets = function() { + var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/'); + return output; +}; diff --git a/config/env/all.js b/config/env/all.js new file mode 100755 index 00000000..c8386400 --- /dev/null +++ b/config/env/all.js @@ -0,0 +1,84 @@ +'use strict'; + +module.exports = { + app: { + title: 'MedForms', + description: 'Generate Forms from PDFs', + keywords: 'typeform, pdfs, forms, generator, form generator', + }, + port: process.env.PORT || 3000, + templateEngine: 'swig', + // The secret should be set to a non-guessable string that + // is used to compute a session hash + sessionSecret: 'MEAN', + // The name of the MongoDB collection to store sessions in + sessionCollection: 'sessions', + // The session cookie settings + sessionCookie: { + path: '/', + httpOnly: true, + // If secure is set to true then it will cause the cookie to be set + // only when SSL-enabled (HTTPS) is used, and otherwise it won't + // set a cookie. 'true' is recommended yet it requires the above + // mentioned pre-requisite. + secure: false, + // Only set the maxAge to null if the cookie shouldn't be expired + // at all. The cookie will expunge when the browser is closed. + maxAge: null, + // To set the cookie in a specific domain uncomment the following + // setting: + // domain: 'yourdomain.com' + }, + + /* + * Upload Configuration + */ + //Global upload path + uploadPath : 'uploads/', + //PDF storage path + pdfUploadPath: 'uploads/pdfs/', + //Temp files storage path + tmpUploadPath: 'uploads/tmp/', + + // The session cookie name + sessionName: 'connect.sid', + log: { + // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' + format: 'combined', + // Stream defaults to process.stdout + // Uncomment to enable logging to a log on the file system + options: { + stream: 'access.log' + } + }, + assets: { + lib: { + css: [ + 'public/lib/bootstrap/dist/css/bootstrap.css', + 'public/lib/bootstrap/dist/css/bootstrap-theme.css', + ], + js: [ + 'public/lib/angular/angular.js', + 'public/lib/angular-resource/angular-resource.js', + 'public/lib/angular-animate/angular-animate.js', + 'public/lib/angular-ui-router/release/angular-ui-router.js', + 'public/lib/angular-ui-utils/ui-utils.js', + 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', + 'public/lib/ng-file-upload/ng-file-upload-all.js' + ] + }, + css: [ + 'public/modules/**/css/*.css' + ], + js: [ + 'public/config.js', + 'public/application.js', + 'public/modules/*/*.js', + 'public/modules/*/*[!tests]*/*.js' + ], + tests: [ + 'public/lib/angular-mocks/angular-mocks.js', + 'public/modules/*/tests/*.js' + ] + } +}; diff --git a/config/env/development.js b/config/env/development.js new file mode 100755 index 00000000..af9f27d9 --- /dev/null +++ b/config/env/development.js @@ -0,0 +1,58 @@ +'use strict'; + +module.exports = { + db: { + uri: 'mongodb://localhost/mean-dev', + options: { + user: '', + pass: '' + } + }, + log: { + // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' + format: 'dev', + // Stream defaults to process.stdout + // Uncomment to enable logging to a log on the file system + options: { + //stream: 'access.log' + } + }, + app: { + title: 'MedForms' + }, + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: '/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: '/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: '/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: '/auth/github/callback' + }, + mailer: { + from: process.env.MAILER_FROM || 'MAILER_FROM', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } + } +}; diff --git a/config/env/production.js b/config/env/production.js new file mode 100755 index 00000000..a56a48f0 --- /dev/null +++ b/config/env/production.js @@ -0,0 +1,73 @@ +'use strict'; + +module.exports = { + db: { + uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', + options: { + user: '', + pass: '' + } + }, + log: { + // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' + format: 'combined', + // Stream defaults to process.stdout + // Uncomment to enable logging to a log on the file system + options: { + stream: 'access.log' + } + }, + assets: { + lib: { + css: [ + 'public/lib/bootstrap/dist/css/bootstrap.min.css', + 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', + ], + js: [ + 'public/lib/angular/angular.min.js', + 'public/lib/angular-resource/angular-resource.min.js', + 'public/lib/angular-animate/angular-animate.min.js', + 'public/lib/angular-ui-router/release/angular-ui-router.min.js', + 'public/lib/angular-ui-utils/ui-utils.min.js', + 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' + ] + }, + css: 'public/dist/application.min.css', + js: 'public/dist/application.min.js' + }, + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: '/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: '/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: '/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: '/auth/github/callback' + }, + mailer: { + from: process.env.MAILER_FROM || 'MAILER_FROM', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } + } +}; diff --git a/config/env/secure.js b/config/env/secure.js new file mode 100755 index 00000000..320d0fb5 --- /dev/null +++ b/config/env/secure.js @@ -0,0 +1,74 @@ +'use strict'; + +module.exports = { + port: 8443, + db: { + uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean', + options: { + user: '', + pass: '' + } + }, + log: { + // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' + format: 'combined', + // Stream defaults to process.stdout + // Uncomment to enable logging to a log on the file system + options: { + stream: 'access.log' + } + }, + assets: { + lib: { + css: [ + 'public/lib/bootstrap/dist/css/bootstrap.min.css', + 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', + ], + js: [ + 'public/lib/angular/angular.min.js', + 'public/lib/angular-resource/angular-resource.min.js', + 'public/lib/angular-animate/angular-animate.min.js', + 'public/lib/angular-ui-router/release/angular-ui-router.min.js', + 'public/lib/angular-ui-utils/ui-utils.min.js', + 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' + ] + }, + css: 'public/dist/application.min.css', + js: 'public/dist/application.min.js' + }, + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: 'https://localhost:443/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: 'https://localhost:443/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: 'https://localhost:443/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: 'https://localhost:443/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: 'https://localhost:443/auth/github/callback' + }, + mailer: { + from: process.env.MAILER_FROM || 'MAILER_FROM', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } + } +}; diff --git a/config/env/test.js b/config/env/test.js new file mode 100755 index 00000000..02e10172 --- /dev/null +++ b/config/env/test.js @@ -0,0 +1,59 @@ +'use strict'; + +module.exports = { + db: { + uri: 'mongodb://localhost/mean-test', + options: { + user: '', + pass: '' + } + }, + port: 3001, + log: { + // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' + format: 'dev', + // Stream defaults to process.stdout + // Uncomment to enable logging to a log on the file system + options: { + //stream: 'access.log' + } + }, + app: { + title: 'MedForms' + }, + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: '/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: '/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: '/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: '/auth/github/callback' + }, + mailer: { + from: process.env.MAILER_FROM || 'MAILER_FROM', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } + } +}; diff --git a/config/express.js b/config/express.js new file mode 100755 index 00000000..135faa44 --- /dev/null +++ b/config/express.js @@ -0,0 +1,196 @@ +'use strict'; + +/** + * Module dependencies. + */ +var fs = require('fs-extra'), + http = require('http'), + https = require('https'), + express = require('express'), + morgan = require('morgan'), + logger = require('./logger'), + bodyParser = require('body-parser'), + session = require('express-session'), + compression = require('compression'), + methodOverride = require('method-override'), + cookieParser = require('cookie-parser'), + helmet = require('helmet'), + multer = require('multer'), + passport = require('passport'), + mongoStore = require('connect-mongo')({ + session: session + }), + flash = require('connect-flash'), + config = require('./config'), + consolidate = require('consolidate'), + path = require('path'); + +module.exports = function(db) { + // Initialize express app + var app = express(); + + // Globbing model files + config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) { + require(path.resolve(modelPath)); + }); + + // Setting application local variables + app.locals.title = config.app.title; + app.locals.description = config.app.description; + app.locals.keywords = config.app.keywords; + app.locals.facebookAppId = config.facebook.clientID; + app.locals.jsFiles = config.getJavaScriptAssets(); + app.locals.cssFiles = config.getCSSAssets(); + + // Passing the request url to environment locals + app.use(function(req, res, next) { + res.locals.url = req.protocol + '://' + req.headers.host + req.url; + next(); + }); + + // Should be placed before express.static + app.use(compression({ + // only compress files for the following content types + filter: function(req, res) { + return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); + }, + // zlib option for compression level + level: 3 + })); + + // Showing stack errors + app.set('showStackError', true); + + // Set swig as the template engine + app.engine('server.view.html', consolidate[config.templateEngine]); + + // Set views path and view engine + app.set('view engine', 'server.view.html'); + app.set('views', './app/views'); + + // Enable logger (morgan) + app.use(morgan(logger.getLogFormat(), logger.getLogOptions())); + + // Environment dependent middleware + if (process.env.NODE_ENV === 'development') { + // Disable views cache + app.set('view cache', false); + } else if (process.env.NODE_ENV === 'production') { + app.locals.cache = 'memory'; + } + + // Request body parsing middleware should be above methodOverride + app.use(bodyParser.urlencoded({ + extended: true + })); + app.use(bodyParser.json()); + app.use(methodOverride()); + + // Use helmet to secure Express headers + app.use(helmet.xframe()); + app.use(helmet.xssFilter()); + app.use(helmet.nosniff()); + app.use(helmet.ienoopen()); + app.disable('x-powered-by'); + + // Setting the app router and static folder + app.use(express.static(path.resolve('./public'))); + + // Setting the pdf upload route and folder + app.use(multer({ dest: config.tmpUploadPath, + rename: function (fieldname, filename) { + return Date.now(); + }, + // changeDest: function(dest, req, res) { + // console.log(req.body.form); + + // var newDestination = dest + req.body.form.title; + // var stat = null; + // try { + // stat = fs.statSync(newDestination); + // } catch (err) { + // fs.mkdirSync(newDestination); + // } + // if (stat && !stat.isDirectory()) { + // console.log('Directory cannot be created'); + // throw new Error('Directory cannot be created because an inode of a different type exists at "' + dest + '"'); + // } + // return newDestination; + // }, + onFileUploadStart: function (file) { + console.log(file.originalname + ' is starting ...'); + }, + onFileUploadComplete: function (file) { + console.log(file.fieldname + ' uploaded to ' + file.path); + // done=true; + } + })); + + // CookieParser should be above session + app.use(cookieParser()); + + // Express MongoDB session storage + app.use(session({ + saveUninitialized: true, + resave: true, + secret: config.sessionSecret, + store: new mongoStore({ + db: db.connection.db, + collection: config.sessionCollection + }), + cookie: config.sessionCookie, + name: config.sessionName + })); + + // use passport session + app.use(passport.initialize()); + app.use(passport.session()); + + // connect flash for flash messages + app.use(flash()); + + // Globbing routing files + config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) { + require(path.resolve(routePath))(app); + }); + + // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. + app.use(function(err, req, res, next) { + // If the error object doesn't exists + if (!err) return next(); + + // Log it + console.error(err.stack); + + // Error page + res.status(500).render('500', { + error: err.stack + }); + }); + + // Assume 404 since no middleware responded + app.use(function(req, res) { + res.status(404).render('404', { + url: req.originalUrl, + error: 'Not Found' + }); + }); + + if (process.env.NODE_ENV === 'secure') { + // Load SSL key and certificate + var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8'); + var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8'); + + // Create HTTPS Server + var httpsServer = https.createServer({ + key: privateKey, + cert: certificate + }, app); + + // Return HTTPS server instance + return httpsServer; + } + + // Return Express server instance + return app; +}; diff --git a/config/init.js b/config/init.js new file mode 100755 index 00000000..3a5b1e52 --- /dev/null +++ b/config/init.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Module dependencies. + */ +var glob = require('glob'), + chalk = require('chalk'); + +/** + * Module init function. + */ +module.exports = function() { + /** + * Before we begin, lets set the environment variable + * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV + */ + glob('./config/env/' + process.env.NODE_ENV + '.js', { + sync: true + }, function(err, environmentFiles) { + if (!environmentFiles.length) { + if (process.env.NODE_ENV) { + console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); + } else { + console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); + } + + process.env.NODE_ENV = 'development'; + } + }); + +}; diff --git a/config/logger.js b/config/logger.js new file mode 100755 index 00000000..9aba2172 --- /dev/null +++ b/config/logger.js @@ -0,0 +1,36 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var morgan = require('morgan'); +var config = require('./config'); +var fs = require('fs-extra'); + +/** + * Module init function. + */ +module.exports = { + + getLogFormat: function() { + return config.log.format; + }, + + getLogOptions: function() { + var options = {}; + + try { + if ('stream' in config.log.options) { + options = { + stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'}) + }; + } + } catch (e) { + options = {}; + } + + return options; + } + +}; diff --git a/config/passport.js b/config/passport.js new file mode 100755 index 00000000..5abfae76 --- /dev/null +++ b/config/passport.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + User = require('mongoose').model('User'), + path = require('path'), + config = require('./config'); + +/** + * Module init function. + */ +module.exports = function() { + // Serialize sessions + passport.serializeUser(function(user, done) { + done(null, user.id); + }); + + // Deserialize sessions + passport.deserializeUser(function(id, done) { + User.findOne({ + _id: id + }, '-salt -password', function(err, user) { + done(err, user); + }); + }); + + // Initialize strategies + config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) { + require(path.resolve(strategy))(); + }); +}; diff --git a/config/strategies/facebook.js b/config/strategies/facebook.js new file mode 100755 index 00000000..34ddc68f --- /dev/null +++ b/config/strategies/facebook.js @@ -0,0 +1,41 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + FacebookStrategy = require('passport-facebook').Strategy, + config = require('../config'), + users = require('../../app/controllers/users.server.controller'); + +module.exports = function() { + // Use facebook strategy + passport.use(new FacebookStrategy({ + clientID: config.facebook.clientID, + clientSecret: config.facebook.clientSecret, + callbackURL: config.facebook.callbackURL, + passReqToCallback: true + }, + function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var providerUserProfile = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'facebook', + providerIdentifierField: 'id', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/config/strategies/github.js b/config/strategies/github.js new file mode 100755 index 00000000..f10a413e --- /dev/null +++ b/config/strategies/github.js @@ -0,0 +1,46 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + GithubStrategy = require('passport-github').Strategy, + config = require('../config'), + users = require('../../app/controllers/users.server.controller'); + +module.exports = function() { + // Use github strategy + passport.use(new GithubStrategy({ + clientID: config.github.clientID, + clientSecret: config.github.clientSecret, + callbackURL: config.github.callbackURL, + passReqToCallback: true + }, + function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var displayName = profile.displayName.trim(); + var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName + var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName; + var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : ''; + + var providerUserProfile = { + firstName: firstName, + lastName: lastName, + displayName: displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'github', + providerIdentifierField: 'id', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/config/strategies/google.js b/config/strategies/google.js new file mode 100755 index 00000000..8044ed4e --- /dev/null +++ b/config/strategies/google.js @@ -0,0 +1,41 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, + config = require('../config'), + users = require('../../app/controllers/users.server.controller'); + +module.exports = function() { + // Use google strategy + passport.use(new GoogleStrategy({ + clientID: config.google.clientID, + clientSecret: config.google.clientSecret, + callbackURL: config.google.callbackURL, + passReqToCallback: true + }, + function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var providerUserProfile = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'google', + providerIdentifierField: 'id', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/config/strategies/linkedin.js b/config/strategies/linkedin.js new file mode 100755 index 00000000..1ee5b3f5 --- /dev/null +++ b/config/strategies/linkedin.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + LinkedInStrategy = require('passport-linkedin').Strategy, + config = require('../config'), + users = require('../../app/controllers/users.server.controller'); + +module.exports = function() { + // Use linkedin strategy + passport.use(new LinkedInStrategy({ + consumerKey: config.linkedin.clientID, + consumerSecret: config.linkedin.clientSecret, + callbackURL: config.linkedin.callbackURL, + passReqToCallback: true, + profileFields: ['id', 'first-name', 'last-name', 'email-address'] + }, + function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var providerUserProfile = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'linkedin', + providerIdentifierField: 'id', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/config/strategies/local.js b/config/strategies/local.js new file mode 100755 index 00000000..ad560524 --- /dev/null +++ b/config/strategies/local.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + LocalStrategy = require('passport-local').Strategy, + User = require('mongoose').model('User'); + +module.exports = function() { + // Use local strategy + passport.use(new LocalStrategy({ + usernameField: 'username', + passwordField: 'password' + }, + function(username, password, done) { + User.findOne({ + username: username + }, function(err, user) { + if (err) { + return done(err); + } + if (!user) { + return done(null, false, { + message: 'Unknown user or invalid password' + }); + } + if (!user.authenticate(password)) { + return done(null, false, { + message: 'Unknown user or invalid password' + }); + } + + return done(null, user); + }); + } + )); +}; diff --git a/config/strategies/twitter.js b/config/strategies/twitter.js new file mode 100755 index 00000000..5dcc93f4 --- /dev/null +++ b/config/strategies/twitter.js @@ -0,0 +1,45 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + TwitterStrategy = require('passport-twitter').Strategy, + config = require('../config'), + users = require('../../app/controllers/users.server.controller'); + +module.exports = function() { + // Use twitter strategy + passport.use(new TwitterStrategy({ + consumerKey: config.twitter.clientID, + consumerSecret: config.twitter.clientSecret, + callbackURL: config.twitter.callbackURL, + passReqToCallback: true + }, + function(req, token, tokenSecret, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.token = token; + providerData.tokenSecret = tokenSecret; + + // Create the user OAuth profile + var displayName = profile.displayName.trim(); + var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName + var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName; + var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : ''; + + var providerUserProfile = { + firstName: firstName, + lastName: lastName, + displayName: displayName, + username: profile.username, + provider: 'twitter', + providerIdentifierField: 'id_str', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/fig.yml b/fig.yml new file mode 100755 index 00000000..967ac7d1 --- /dev/null +++ b/fig.yml @@ -0,0 +1,12 @@ +web: + build: . + links: + - db + ports: + - "3000:3000" + environment: + NODE_ENV: development +db: + image: mongo + ports: + - "27017:27017" diff --git a/gruntfile.js b/gruntfile.js new file mode 100755 index 00000000..b45a9cd7 --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,183 @@ +'use strict'; + +module.exports = function(grunt) { + // Unified Watch Object + var watchFiles = { + serverViews: ['app/views/**/*.*'], + serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'], + clientViews: ['public/modules/**/views/**/*.html'], + clientJS: ['public/js/*.js', 'public/modules/**/*.js'], + clientCSS: ['public/modules/**/*.css'], + mochaTests: ['app/tests/**/*.js'] + }; + + // Project Configuration + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + watch: { + serverViews: { + files: watchFiles.serverViews, + options: { + livereload: true + } + }, + serverJS: { + files: watchFiles.serverJS, + tasks: ['jshint'], + options: { + livereload: true + } + }, + clientViews: { + files: watchFiles.clientViews, + options: { + livereload: true + } + }, + clientJS: { + files: watchFiles.clientJS, + tasks: ['jshint'], + options: { + livereload: true + } + }, + clientCSS: { + files: watchFiles.clientCSS, + tasks: ['csslint'], + options: { + livereload: true + } + }, + mochaTests: { + files: watchFiles.mochaTests, + tasks: ['test:server'], + } + }, + jshint: { + all: { + src: watchFiles.clientJS.concat(watchFiles.serverJS), + options: { + jshintrc: true + } + } + }, + csslint: { + options: { + csslintrc: '.csslintrc' + }, + all: { + src: watchFiles.clientCSS + } + }, + uglify: { + production: { + options: { + mangle: false + }, + files: { + 'public/dist/application.min.js': 'public/dist/application.js' + } + } + }, + cssmin: { + combine: { + files: { + 'public/dist/application.min.css': '<%= applicationCSSFiles %>' + } + } + }, + nodemon: { + dev: { + script: 'server.js', + options: { + nodeArgs: ['--debug'], + ext: 'js,html', + watch: watchFiles.serverViews.concat(watchFiles.serverJS) + } + } + }, + 'node-inspector': { + custom: { + options: { + 'web-port': 1337, + 'web-host': 'localhost', + 'debug-port': 5858, + 'save-live-edit': true, + 'no-preload': true, + 'stack-trace-limit': 50, + 'hidden': [] + } + } + }, + ngAnnotate: { + production: { + files: { + 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' + } + } + }, + concurrent: { + default: ['nodemon', 'watch'], + debug: ['nodemon', 'watch', 'node-inspector'], + options: { + logConcurrentOutput: true, + limit: 10 + } + }, + env: { + test: { + NODE_ENV: 'test' + }, + secure: { + NODE_ENV: 'secure' + } + }, + mochaTest: { + src: watchFiles.mochaTests, + options: { + reporter: 'spec', + require: 'server.js' + } + }, + karma: { + unit: { + configFile: 'karma.conf.js' + } + } + }); + + // Load NPM tasks + require('load-grunt-tasks')(grunt); + + // Making grunt default to force in order not to break the project. + grunt.option('force', true); + + // A Task for loading the configuration object + grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { + var init = require('./config/init')(); + var config = require('./config/config'); + + grunt.config.set('applicationJavaScriptFiles', config.assets.js); + grunt.config.set('applicationCSSFiles', config.assets.css); + }); + + // Default task(s). + grunt.registerTask('default', ['lint', 'concurrent:default']); + + // Debug task. + grunt.registerTask('debug', ['lint', 'concurrent:debug']); + + // Secure task(s). + grunt.registerTask('secure', ['env:secure', 'lint', 'concurrent:default']); + + // Lint task(s). + grunt.registerTask('lint', ['jshint', 'csslint']); + + // Build task(s). + grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']); + + // Test task. + grunt.registerTask('test', ['test:server', 'test:client']); + grunt.registerTask('test:server', ['env:test', 'mochaTest']); + grunt.registerTask('test:client', ['env:test', 'karma:unit']); +}; diff --git a/karma.conf.js b/karma.conf.js new file mode 100755 index 00000000..0f5ab311 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,51 @@ +'use strict'; + +/** + * Module dependencies. + */ +var applicationConfiguration = require('./config/config'); + +// Karma configuration +module.exports = function(config) { + config.set({ + // Frameworks to use + frameworks: ['jasmine'], + + // List of files / patterns to load in the browser + files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests), + + // Test results reporter to use + // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' + reporters: ['progress'], + + // Web server port + port: 9876, + + // Enable / disable colors in the output (reporters and logs) + colors: true, + + // Level of logging + // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // Enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['PhantomJS'], + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + // Continuous Integration mode + // If true, it capture browsers, run tests and exit + singleRun: true + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..3250e0e3 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "meanjs", + "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.", + "version": "0.3.3", + "private": false, + "author": "https://github.com/meanjs/mean/graphs/contributors", + "repository": { + "type": "git", + "url": "https://github.com/meanjs/mean.git" + }, + "engines": { + "node": ">=0.10.28", + "npm": ">=1.4.28" + }, + "scripts": { + "start": "grunt", + "test": "grunt test", + "postinstall": "bower install --config.interactive=false" + }, + "dependencies": { + "async": "~0.9.0", + "body-parser": "~1.9.0", + "bower": "~1.3.8", + "chalk": "~1.0.0", + "compression": "~1.2.0", + "connect-flash": "~0.1.1", + "connect-mongo": "~0.4.1", + "consolidate": "~0.10.0", + "cookie-parser": "~1.3.2", + "express": "~4.10.1", + "express-session": "~1.9.1", + "forever": "~0.11.0", + "fs-extra": "^0.18.3", + "glob": "~4.0.5", + "grunt-cli": "~0.1.13", + "helmet": "~0.5.0", + "lodash": "~2.4.1", + "method-override": "~2.3.0", + "mongoose": "~3.8.8", + "morgan": "~1.4.1", + "nodemailer": "~1.3.0", + "passport": "~0.2.0", + "passport-facebook": "~1.0.2", + "passport-github": "~0.1.5", + "passport-google-oauth": "~0.1.5", + "passport-linkedin": "~0.1.3", + "passport-local": "~1.0.0", + "passport-twitter": "~1.0.2", + "satelize": "^0.1.1", + "swig": "~1.4.1" + }, + "devDependencies": { + "supertest": "~0.14.0", + "should": "~4.1.0", + "grunt-env": "~0.4.1", + "grunt-node-inspector": "~0.1.3", + "grunt-contrib-watch": "~0.6.1", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-csslint": "^0.3.1", + "grunt-ng-annotate": "~0.4.0", + "grunt-contrib-uglify": "~0.6.0", + "grunt-contrib-cssmin": "~0.10.0", + "grunt-nodemon": "~0.3.0", + "grunt-concurrent": "~1.0.0", + "grunt-mocha-test": "~0.12.1", + "grunt-karma": "~0.9.0", + "load-grunt-tasks": "~1.0.0", + "karma": "~0.12.0", + "karma-jasmine": "~0.2.1", + "karma-coverage": "~0.2.0", + "karma-chrome-launcher": "~0.1.2", + "karma-firefox-launcher": "~0.1.3", + "karma-phantomjs-launcher": "~0.1.2" + } +} diff --git a/public/application.js b/public/application.js new file mode 100755 index 00000000..4b242797 --- /dev/null +++ b/public/application.js @@ -0,0 +1,32 @@ +'use strict'; + +//Start by defining the main module and adding the module dependencies +angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); + +// Setting HTML5 Location Mode +angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', + function($locationProvider) { + $locationProvider.hashPrefix('!'); + } +]); +angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Authorization', 'Principal', + function($rootScope, Authorization, Principal) { + $rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) { + // track the state the user wants to go to; authorization service needs this + $rootScope.toState = toState; + $rootScope.toStateParams = toStateParams; + // if the principal is resolved, do an authorization check immediately. otherwise, + // it'll be done when the state it resolved. + if (Principal.isIdentityResolved()) Authorization.authorize(); + }); + } +]); + +//Then define the init function for starting up the application +angular.element(document).ready(function() { + //Fixing facebook bug with redirect + if (window.location.hash === '#_=_') window.location.hash = '#!'; + + //Then init the app + angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); +}); \ No newline at end of file diff --git a/public/config.js b/public/config.js new file mode 100755 index 00000000..109385a5 --- /dev/null +++ b/public/config.js @@ -0,0 +1,23 @@ +'use strict'; + +// Init the application configuration module for AngularJS application +var ApplicationConfiguration = (function() { + // Init module configuration options + var applicationModuleName = 'medform'; + var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils']; + + // Add a new vertical module + var registerModule = function(moduleName, dependencies) { + // Create angular module + angular.module(moduleName, dependencies || []); + + // Add the module to the AngularJS configuration file + angular.module(applicationModuleName).requires.push(moduleName); + }; + + return { + applicationModuleName: applicationModuleName, + applicationModuleVendorDependencies: applicationModuleVendorDependencies, + registerModule: registerModule + }; +})(); \ No newline at end of file diff --git a/public/humans.txt b/public/humans.txt new file mode 100755 index 00000000..5b037cf2 --- /dev/null +++ b/public/humans.txt @@ -0,0 +1,15 @@ +# humanstxt.org/ +# The humans responsible & technology colophon + +# TEAM + + -- -- + +# THANKS + + + +# TECHNOLOGY COLOPHON + + HTML5, CSS3 + jQuery, Modernizr diff --git a/public/modules/core/config/core.client.routes.js b/public/modules/core/config/core.client.routes.js new file mode 100755 index 00000000..5752a1aa --- /dev/null +++ b/public/modules/core/config/core.client.routes.js @@ -0,0 +1,26 @@ +'use strict'; + +// Setting up route +angular.module('core').config(['$stateProvider', '$urlRouterProvider', + function($stateProvider, $urlRouterProvider, Authorization) { + // Redirect to home view when route not found + $urlRouterProvider.otherwise('/'); + + // Home state routing + $stateProvider. + state('home', { + url: '/', + templateUrl: 'modules/core/views/home.client.view.html' + }). + state('restricted', { + 'abstract': true, + resolve: { + authorize: ['Authorization', + function(Authorization) { + return Authorization.authorize(); + } + ] + } + }); + } +]); \ No newline at end of file diff --git a/public/modules/core/controllers/header.client.controller.js b/public/modules/core/controllers/header.client.controller.js new file mode 100755 index 00000000..cfc8820e --- /dev/null +++ b/public/modules/core/controllers/header.client.controller.js @@ -0,0 +1,40 @@ +'use strict'; + +angular.module('core').controller('HeaderController', ['$scope', 'Principal', 'Menus', '$state', + function($scope, Principal, Menus, $state) { + $scope.authentication = Principal; + $scope.isCollapsed = false; + $scope.hideNav = false; + $scope.menu = Menus.getMenu('topbar'); + + Principal.identity().then(function(user){ + $scope.authentication.user = user; + }).then(function(){ + $scope.signout = function() { + var response_obj = Principal.signout(); + if( angular.isDefined(response_obj.error) ){ + $scope.error = response_obj.error; + } else{ + $state.go('home'); + } + }; + + $scope.toggleCollapsibleMenu = function() { + $scope.isCollapsed = !$scope.isCollapsed; + }; + + // Collapsing the menu after navigation + $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) { + $scope.isCollapsed = false; + $scope.hideNav = false; + if ( angular.isDefined( toState.data ) ) { + + if ( angular.isDefined( toState.data.hideNav ) ) { + $scope.hideNav = toState.data.hideNav; + } + } + }); + }); + + } +]); \ No newline at end of file diff --git a/public/modules/core/controllers/home.client.controller.js b/public/modules/core/controllers/home.client.controller.js new file mode 100755 index 00000000..f0bdce33 --- /dev/null +++ b/public/modules/core/controllers/home.client.controller.js @@ -0,0 +1,15 @@ +'use strict'; + + +angular.module('core').controller('HomeController', ['$scope', 'Principal', + function($scope, Principal) { + // This provides Principal context. + $scope.authentication = Principal; + $scope.authentication.user = undefined; + Principal.identity().then(function(user){ + $scope.authentication.user = user; + }); + // console.log("user.displayName: "+Principal.user()._id); + + } +]); \ No newline at end of file diff --git a/public/modules/core/core.client.module.js b/public/modules/core/core.client.module.js new file mode 100755 index 00000000..186aec42 --- /dev/null +++ b/public/modules/core/core.client.module.js @@ -0,0 +1,4 @@ +'use strict'; + +// Use Application configuration module to register a new module +ApplicationConfiguration.registerModule('core', ['users']); diff --git a/public/modules/core/css/core.css b/public/modules/core/css/core.css new file mode 100755 index 00000000..f20a04c9 --- /dev/null +++ b/public/modules/core/css/core.css @@ -0,0 +1,20 @@ +.content { + margin-top: 50px; +} +.undecorated-link:hover { + text-decoration: none; +} +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} +.ng-invalid.ng-dirty { + border-color: #FA787E; +} +.ng-valid.ng-dirty { + border-color: #78FA89; +} +.browsehappy.jumbotron.hide, +body.ng-cloak +{ + display: block; +} diff --git a/public/modules/core/img/brand/favicon.ico b/public/modules/core/img/brand/favicon.ico new file mode 100755 index 00000000..756ec7e8 Binary files /dev/null and b/public/modules/core/img/brand/favicon.ico differ diff --git a/public/modules/core/img/brand/logo.png b/public/modules/core/img/brand/logo.png new file mode 100755 index 00000000..d28cc87a Binary files /dev/null and b/public/modules/core/img/brand/logo.png differ diff --git a/public/modules/core/img/loaders/loader.gif b/public/modules/core/img/loaders/loader.gif new file mode 100755 index 00000000..f89b233b Binary files /dev/null and b/public/modules/core/img/loaders/loader.gif differ diff --git a/public/modules/core/services/menus.client.service.js b/public/modules/core/services/menus.client.service.js new file mode 100755 index 00000000..96d10400 --- /dev/null +++ b/public/modules/core/services/menus.client.service.js @@ -0,0 +1,171 @@ +'use strict'; + +//Menu service used for managing menus +angular.module('core').service('Menus', [ + + function() { + // Define a set of default roles + this.defaultRoles = ['*']; + + // Define the menus object + this.menus = {}; + + // A private function for rendering decision + var shouldRender = function(user) { + if (user) { + if (!!~this.roles.indexOf('*')) { + return true; + } else { + for (var userRoleIndex in user.roles) { + for (var roleIndex in this.roles) { + console.log(this.roles[roleIndex]); + console.log( this.roles[roleIndex] === user.roles[userRoleIndex]); + if (this.roles[roleIndex] === user.roles[userRoleIndex]) { + return true; + } + } + } + } + } else { + return this.isPublic; + } + + return false; + }; + + // Validate menu existance + this.validateMenuExistance = function(menuId) { + if (menuId && menuId.length) { + if (this.menus[menuId]) { + return true; + } else { + throw new Error('Menu does not exists'); + } + } else { + throw new Error('MenuId was not provided'); + } + + return false; + }; + + // Get the menu object by menu id + this.getMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + return this.menus[menuId]; + }; + + // Add new menu object by menu id + this.addMenu = function(menuId, isPublic, roles) { + // Create the new menu + this.menus[menuId] = { + isPublic: isPublic || false, + roles: roles || this.defaultRoles, + items: [], + shouldRender: shouldRender + }; + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + delete this.menus[menuId]; + }; + + // Add menu item object + this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Push new menu item + this.menus[menuId].items.push({ + title: menuItemTitle, + link: menuItemURL, + menuItemType: menuItemType || 'item', + menuItemClass: menuItemType, + uiRoute: menuItemUIRoute || ('/' + menuItemURL), + isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), + roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), + position: position || 0, + items: [], + shouldRender: shouldRender + }); + + // Return the menu object + return this.menus[menuId]; + }; + + // Add submenu item object + this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { + // Push new submenu item + this.menus[menuId].items[itemIndex].items.push({ + title: menuItemTitle, + link: menuItemURL, + uiRoute: menuItemUIRoute || ('/' + menuItemURL), + isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), + roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), + position: position || 0, + shouldRender: shouldRender + }); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenuItem = function(menuId, menuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].link === menuItemURL) { + this.menus[menuId].items.splice(itemIndex, 1); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeSubMenuItem = function(menuId, submenuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { + if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { + this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); + } + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + //Adding the topbar menu + this.addMenu('topbar', false, ['*']); + + //Adding the bottombar menu for the Form-Footer view + this.addMenu('bottombar', false, ['*']); + } +]); \ No newline at end of file diff --git a/public/modules/core/tests/header.client.controller.test.js b/public/modules/core/tests/header.client.controller.test.js new file mode 100755 index 00000000..76ee4fb4 --- /dev/null +++ b/public/modules/core/tests/header.client.controller.test.js @@ -0,0 +1,24 @@ +'use strict'; + +(function() { + describe('HeaderController', function() { + //Initialize global variables + var scope, + HeaderController; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + + HeaderController = $controller('HeaderController', { + $scope: scope + }); + })); + + it('should expose the authentication service', function() { + expect(scope.authentication).toBeTruthy(); + }); + }); +})(); \ No newline at end of file diff --git a/public/modules/core/tests/home.client.controller.test.js b/public/modules/core/tests/home.client.controller.test.js new file mode 100755 index 00000000..a5b1a566 --- /dev/null +++ b/public/modules/core/tests/home.client.controller.test.js @@ -0,0 +1,24 @@ +'use strict'; + +(function() { + describe('HomeController', function() { + //Initialize global variables + var scope, + HomeController; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + + HomeController = $controller('HomeController', { + $scope: scope + }); + })); + + it('should expose the authentication service', function() { + expect(scope.authentication).toBeTruthy(); + }); + }); +})(); \ No newline at end of file diff --git a/public/modules/core/views/header.client.view.html b/public/modules/core/views/header.client.view.html new file mode 100755 index 00000000..719e5e09 --- /dev/null +++ b/public/modules/core/views/header.client.view.html @@ -0,0 +1,60 @@ + \ No newline at end of file diff --git a/public/modules/core/views/home.client.view.html b/public/modules/core/views/home.client.view.html new file mode 100755 index 00000000..68169379 --- /dev/null +++ b/public/modules/core/views/home.client.view.html @@ -0,0 +1,105 @@ +
+
+
+
+ +
+
+
+
+

+ Make beautiful forms in a snap. +

+



+ + Signup now + + +
+
+

+ Hi there {{authentication.user.displayName}} +

+
+
+

+ Learn more +

+
+
+ + +
Enjoy & Keep Us Updated, +
The MedForms Team. +
diff --git a/public/modules/forms/config/forms.client.config.js b/public/modules/forms/config/forms.client.config.js new file mode 100644 index 00000000..d57ecbc1 --- /dev/null +++ b/public/modules/forms/config/forms.client.config.js @@ -0,0 +1,35 @@ +'use strict'; + +// Configuring the Articles module +angular.module('forms').run(['Menus', + function(Menus) { + // Set top bar menu items + Menus.addMenuItem('topbar', 'Forms', 'forms', 'dropdown', '/forms(/create)?'); + Menus.addSubMenuItem('topbar', 'forms', 'List Forms', 'forms'); + Menus.addSubMenuItem('topbar', 'forms', 'Create Form', 'forms/create'); + } +]).filter('formValidity', + function(){ + + return function(formObj){ + //get keys + var formKeys = Object.keys(formObj); + // console.log(formKeys); + //we only care about things that don't start with $ + var fieldKeys = formKeys.filter(function(key){ + return key[0] !== '$'; + }); + + var fields = formObj.form_fields; + // fieldKeys.map(function(key){ + // return formObj[key]; + // }); + + var valid_count = fields.filter(function(field){ + if(typeof field === 'object'){ + return !!(field.fieldValue); + } + }).length; + return valid_count; + }; +}); \ No newline at end of file diff --git a/public/modules/forms/config/forms.client.routes.js b/public/modules/forms/config/forms.client.routes.js new file mode 100644 index 00000000..b0425431 --- /dev/null +++ b/public/modules/forms/config/forms.client.routes.js @@ -0,0 +1,41 @@ +'use strict'; + +// Setting up route +angular.module('forms').config(['$stateProvider', + function($stateProvider) { + // Forms state routing + $stateProvider. + state('listForms', { + url: '/forms', + templateUrl: 'modules/forms/views/list-forms.client.view.html', + }). + state('createForm', { + url: '/forms/create', + templateUrl: 'modules/forms/views/create-form.client.view.html', + // parent: 'restricted', + // data: { + // roles: ['user', 'admin'], + // }, + }). + state('viewForm', { + url: '/forms/:formId/admin', + templateUrl: 'modules/forms/views/view-form.client.view.html', + }). + state('viewPublicForm', { + url: '/forms/:formId', + templateUrl: 'modules/forms/views/view-public-form.client.view.html', + data: { + hideNav: true, + hideFooter: false + }, + }). + state('editForm', { + url: '/forms/:formId/edit', + templateUrl: 'modules/forms/views/create-form.client.view.html', + // parent: 'restricted', + // data: { + // roles: ['user', 'admin'], + // }, + }); + } +]); \ No newline at end of file diff --git a/public/modules/forms/controllers/create-form.client.controller.js b/public/modules/forms/controllers/create-form.client.controller.js new file mode 100644 index 00000000..f7b5761b --- /dev/null +++ b/public/modules/forms/controllers/create-form.client.controller.js @@ -0,0 +1,234 @@ +'use strict'; + +angular.module('forms').controller('EditFormController', ['$scope', '$state', 'Upload', '$stateParams', 'Principal', 'FormFields', 'Forms', 'CurrentForm', '$modal', '$location', + function ($scope, $state, Upload, $stateParams, Principal, FormFields, Forms, CurrentForm, $modal, $location) { + // Principal.identity().then(function(user){ + // $scope.authentication.user = user; + // }).then(function(){ + // console.log('aeouaoeuaoeuaou'); + // console.log('isAuthenticated(): '+Principal.isAuthenticated());\ + + $scope.isNewForm = false; + $scope.log = ''; + + // Get current form if it exists, or create new one + if($stateParams.formId){ + $scope.form = {}; + var _form = Forms.get({ formId: $stateParams.formId}, function(form){ + _form.pdf = form.pdf; + _form.$save(); + + $scope.form = angular.fromJson(angular.toJson(_form)); + console.log(JSON.stringify($scope.form.pdf)); + }); + } else { + $scope.form = {}; + $scope.form.form_fields = []; + $scope.isNewForm = true; + } + + //PDF Functions + + $scope.cancelUpload = function(){ + //TBD + }; + + $scope.removePDF = function(){ + $scope.form.pdf = null; + + console.log('form.pdf exists: '+!!$scope.form.pdf); + }; + + $scope.uploadPDF = function(files) { + + if (files && files.length) { + for (var i = 0; i < files.length; i++) { + var file = files[i]; + Upload.upload({ + url: '/upload/pdf', + fields: { + 'user': $scope.form.admin, + 'form': $scope.form + }, + file: file + }).progress(function (evt) { + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total); + $scope.log = 'progress: ' + progressPercentage + '% ' + + evt.config.file.name + '\n' + $scope.log; + }).success(function (data, status, headers, config) { + $scope.log = 'file ' + data.originalname + 'uploaded as '+ data.name +'. JSON: ' + JSON.stringify(data) + '\n' + $scope.log; + $scope.pdf = data; + $scope.form.pdf = data; + + if(!$scope.$$phase) { + $scope.$apply(); + } + + console.log($scope.log); + console.log('$scope.pdf: '+$scope.pdf.name); + }); + } + } + }; + + $scope.goToWithId = function(route, id) { + $state.transitionTo(route, { 'formId' : id }, { reload: true }); + }; + + // Create new Form + $scope.createOrUpdate = function() { + + if($scope.isNewForm){ + // Create new Form object + var form = new Forms($scope.form); + + + form.$save(function(response) { + + console.log('form created'); + // console.log(response.pdf); + + // Clear form fields + $scope.form = {}; + + // Redirect after save + $location.path('forms/' + response._id + '/admin'); + + }, function(errorResponse) { + console.log(errorResponse.data.message); + $scope.error = errorResponse.data.message; + }); + } else{ + console.log('update form'); + $scope.update(); + } + }; + + // Update existing Form + $scope.update = function() { + var form = new Forms($scope.form); + form.$update(function(response) { + console.log('form updated'); + // console.log(response.pdf); + $location.path('forms/' + response._id + '/admin'); + }, function(errorResponse) { + console.log(errorResponse.data.message); + $scope.error = errorResponse.data.message; + }); + }; + + //Populate AddField with all available form field types + $scope.addField = {}; + $scope.addField.types = FormFields.fields; + $scope.addField.new = $scope.addField.types[0].name; + $scope.addField.lastAddedID = 0; + + + // preview form mode + $scope.previewMode = false; + + // previewForm - for preview purposes, form will be copied into this + // otherwise, actual form might get manipulated in preview mode + $scope.previewForm = {}; + + + // accordion settings + $scope.accordion = {}; + $scope.accordion.oneAtATime = true; + + // create new field button click + $scope.addNewField = function(){ + + // incr field_id counter + $scope.addField.lastAddedID++; + + var newField = { + 'title' : 'New field - ' + ($scope.addField.lastAddedID), + 'fieldType' : $scope.addField.new, + 'fieldValue' : '', + 'required' : true, + 'disabled' : false + }; + + // put newField into fields array + $scope.form.form_fields.push(newField); + // console.log($scope.form.form_fields); + }; + + // deletes particular field on button click + $scope.deleteField = function (field_id){ + for(var i = 0; i < $scope.form.form_fields.length; i++){ + if($scope.form.form_fields[i].field_id === field_id){ + $scope.form.form_fields.splice(i, 1); + break; + } + } + }; + + // add new option to the field + $scope.addOption = function (field){ + if(!field.field_options) + field.field_options = []; + + var lastOptionID = 0; + + if(field.field_options[field.field_options.length-1]) + lastOptionID = field.field_options[field.field_options.length-1].option_id; + + // new option's id + var option_id = lastOptionID + 1; + + var newOption = { + 'option_id' : option_id, + 'option_title' : 'Option ' + option_id, + 'option_value' : option_id + }; + + // put new option into field_options array + field.field_options.push(newOption); + }; + + // delete particular option + $scope.deleteOption = function (field, option){ + for(var i = 0; i < field.field_options.length; i++){ + if(field.field_options[i].option_id === option.option_id){ + field.field_options.splice(i, 1); + break; + } + } + }; + + + // preview form + $scope.previewOn = function(){ + if($scope.form.form_fields === null || $scope.form.form_fields.length === 0) { + var title = 'Error'; + var msg = 'No fields added yet, please add fields to the form before preview.'; + var btns = [{result:'ok', label: 'OK', cssClass: 'btn-primary'}]; + + // $dialog.messageBox(title, msg, btns).open(); + } + else { + $scope.previewMode = !$scope.previewMode; + $scope.form.submitted = false; + angular.copy($scope.form, $scope.previewForm); + } + }; + + // hide preview form, go back to create mode + $scope.previewOff = function(){ + $scope.previewMode = !$scope.previewMode; + $scope.form.submitted = false; + }; + + // decides whether field options block will be shown (true for dropdown and radio fields) + $scope.showAddOptions = function (field){ + if(field.field_type === 'radio' || field.field_type === 'dropdown') + return true; + else + return false; + }; + + // }); + } +]); \ No newline at end of file diff --git a/public/modules/forms/controllers/submit-form.client.controller.js b/public/modules/forms/controllers/submit-form.client.controller.js new file mode 100644 index 00000000..699016fe --- /dev/null +++ b/public/modules/forms/controllers/submit-form.client.controller.js @@ -0,0 +1,22 @@ +'use strict'; + +// Forms controller +angular.module('forms').controller('SubmitFormController', ['$scope', '$stateParams', '$state', 'Principal', 'Forms', 'CurrentForm','$http', + function($scope, $stateParams, $state, Principal, Forms, CurrentForm, $http) { + + // Principal.identity().then(function(user){ + // $scope.authentication.user = user; + // }).then(function(){ + + $scope.form = Forms.get({ + formId: $stateParams.formId + }); + CurrentForm.setForm($scope.form); + + + // console.log($scope.form); + + + // }); + } +]); \ No newline at end of file diff --git a/public/modules/forms/controllers/view-form.client.controller.js b/public/modules/forms/controllers/view-form.client.controller.js new file mode 100644 index 00000000..ca9ba58c --- /dev/null +++ b/public/modules/forms/controllers/view-form.client.controller.js @@ -0,0 +1,55 @@ +'use strict'; + +// Forms controller +angular.module('forms').controller('ViewFormController', ['$scope', '$stateParams', '$state', 'Principal', 'Forms', 'CurrentForm','$http', + function($scope, $stateParams, $state, Principal, Forms, CurrentForm, $http) { + + // Principal.identity().then(function(user){ + // $scope.authentication.user = user; + // }).then(function(){ + + + // Return all user's Forms + $scope.find = function() { + $scope.forms = Forms.query(); + }; + + // Find a specific Form + $scope.findOne = function() { + $scope.form = Forms.get({ + formId: $stateParams.formId + }); + CurrentForm.setForm($scope.form); + }; + + + // Remove existing Form + $scope.remove = function(form) { + if (form) { + form.$remove(); + + $http.delete('/forms/'+$scope.form._id). + success(function(data, status, headers){ + console.log('form deleted successfully'); + alert('Form deleted..'); + $state.go('listForms'); + }); + + } else { + $scope.form.$remove(function() { + console.log('remove'); + $state.path('forms'); + $http.delete('/forms/'+$scope.form._id). + success(function(data, status, headers){ + console.log('form deleted successfully'); + alert('Form deleted..'); + $state.go('listForms'); + }); + }); + } + }; + + + // }); + } +]); \ No newline at end of file diff --git a/public/modules/forms/css/form.css b/public/modules/forms/css/form.css new file mode 100644 index 00000000..790eb681 --- /dev/null +++ b/public/modules/forms/css/form.css @@ -0,0 +1,6865 @@ +/*.ellipsis { + display:block; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + -o-text-overflow:ellipsis; + -ms-text-overflow:ellipsis +} + +.clear { + clear:both +} + +.style-transparent-choice { + position:relative +} + +.style-transparent-choice .aux { + border-color:#3f4237 +} + +.style-transparent-choice .aux,.style-transparent-choice .aux div { + position:absolute; + top:0; + bottom:0; + left:0; + right:0; + border-radius:2px; + -webkit-border-radius:2px; + -moz-border-radius:2px +} + +.style-transparent-choice .aux img { + width:100%; + height:100% +} + +.style-transparent-choice .dark { + display:none +} + +.style-transparent-choice.step5 .dark { + display:block +} + +.style-transparent-choice.step5 .light { + display:none +} + +.style-transparent-choice.step0 .aux img { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.style-transparent-choice.step1 .aux img { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.style-transparent-choice.step2 .aux img { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.style-transparent-choice.step3 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.style-transparent-choice.step4 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.style-transparent-choice.step5 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.style-transparent-choice .bg { + z-index:2; + -webkit-box-shadow:0 1px 1px #000; + -moz-box-shadow:0 1px 1px #000; + box-shadow:0 1px 1px #000 +} + +.style-transparent-choice.step0 .bg { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.style-transparent-choice.step1 .bg { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.style-transparent-choice.step2 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.style-transparent-choice.step3 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.style-transparent-choice.step4 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.style-transparent-choice.step5 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.style-transparent-choice .inset { + z-index:3; + -webkit-box-shadow:inset 0 2px 0 #fff; + -moz-box-shadow:inset 0 2px 0 #fff; + box-shadow:inset 0 2px 0 #fff +} + +.style-transparent-choice.step0 .inset { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.style-transparent-choice.step1 .inset { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.style-transparent-choice.step2 .inset { + -khtml-opacity:.25; + -moz-opacity:.25; + opacity:.25; + -ms-filter:alpha(Opacity=25) +} + +.style-transparent-choice.step3 .inset { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.style-transparent-choice.step4 .inset { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.style-transparent-choice.step5 .inset { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.style-transparent-choice .bd { + z-index:4; + border:1px solid #000 +} + +.style-transparent-choice.without-padding .inset { + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0 +} + +.style-transparent-choice.without-padding .bd { + border-left:0; + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0 +} + +.style-transparent-choice.without-padding.first .bd { + border-left-style:solid; + border-left-width:1px +} + +.style-transparent-choice.without-padding.first .bd,.style-transparent-choice.without-padding.first .background,.style-transparent-choice.without-padding.first .inset { + border-radius:2px 0 0 2px; + -webkit-border-radius:2px 0 0 2px; + -moz-border-radius:2px 0 0 2px +} + +.style-transparent-choice.without-padding.last .bd,.style-transparent-choice.without-padding.last .background,.style-transparent-choice.without-padding.last .inset { + border-radius:0 2px 2px 0; + -webkit-border-radius:0 2px 2px 0; + -moz-border-radius:0 2px 2px 0 +} + +.style-transparent-choice.step0 .bd { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.style-transparent-choice.step1 .bd { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.style-transparent-choice.step2 .bd { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.style-transparent-choice.step3 .bd { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.style-transparent-choice.step4 .bd { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.style-transparent-choice.step5 .bd { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15); + border-color:#fff +} + +.style-transparent-choice .tick { + display:none; + position:absolute; + right:7px; + top:50%; + margin-top:-15px; + font-size:21px; + -khtml-opacity:.7; + -moz-opacity:.7; + opacity:.7; + -ms-filter:alpha(Opacity=70) +} + +.style-transparent-choice .tick:before { + font-family:typeIconFont; + content:"H" +} + +.style-transparent-choice.selected .tick { + display:block +} + +.style-transparent-choice.selected.step0 .overlay { + -khtml-opacity:.03; + -moz-opacity:.03; + opacity:.03; + -ms-filter:alpha(Opacity=3); + display:block; + background-color:#000 +} + +.style-transparent-choice.selected.step1 .overlay { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10); + display:block; + background-color:#fff +} + +.style-transparent-choice.selected.step2 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.style-transparent-choice.selected.step3 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.style-transparent-choice.selected.step4 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.style-transparent-choice.selected.step5 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.style-transparent-choice .overlay { + background-color:#000; + background-color:rgba(0,0,0,0); + z-index:1 +} + +.style-transparent-choice.step0 .overlay { + background-color:#000; + background-color:rgba(0,0,0,0) +} + +.style-transparent-choice.step1 .overlay,.style-transparent-choice.step2 .overlay,.style-transparent-choice.step3 .overlay,.style-transparent-choice.step4 .overlay,.style-transparent-choice.step5 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,0) +} + +.style-transparent-choice.pre-selected.step0 .overlay,.style-transparent-choice.active.step0 .overlay { + background-color:#000; + background-color:rgba(0,0,0,.05) +} + +.style-transparent-choice.pre-selected.step0 .bd,.style-transparent-choice.active.step0 .bd { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.style-transparent-choice.pre-selected.step1 .overlay,.style-transparent-choice.active.step1 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.2) +} + +.style-transparent-choice.pre-selected.step1 .bd,.style-transparent-choice.active.step1 .bd { + -khtml-opacity:.35; + -moz-opacity:.35; + opacity:.35; + -ms-filter:alpha(Opacity=35) +} + +.style-transparent-choice.pre-selected.step2 .overlay,.style-transparent-choice.active.step2 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.style-transparent-choice.pre-selected.step2 .bd,.style-transparent-choice.active.step2 .bd { + -khtml-opacity:.45; + -moz-opacity:.45; + opacity:.45; + -ms-filter:alpha(Opacity=45) +} + +.style-transparent-choice.pre-selected.step3 .overlay,.style-transparent-choice.active.step3 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.style-transparent-choice.pre-selected.step3 .bd,.style-transparent-choice.active.step3 .bd { + -khtml-opacity:.55; + -moz-opacity:.55; + opacity:.55; + -ms-filter:alpha(Opacity=55) +} + +.style-transparent-choice.pre-selected.step4 .overlay,.style-transparent-choice.active.step4 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.style-transparent-choice.pre-selected.step4 .bd,.style-transparent-choice.active.step4 .bd { + -khtml-opacity:.65; + -moz-opacity:.65; + opacity:.65; + -ms-filter:alpha(Opacity=65) +} + +.style-transparent-choice.pre-selected.step5 .overlay,.style-transparent-choice.active.step5 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.style-transparent-choice.pre-selected.step5 .bd,.style-transparent-choice.active.step5 .bd { + -khtml-opacity:.25; + -moz-opacity:.25; + opacity:.25; + -ms-filter:alpha(Opacity=25) +} + +.style-transparent-choice:hover .no-hover .overlay { + display:none +} + +.style-transparent-choice .line { + position:absolute; + height:10px; + bottom:-10px; + width:1px; + border-left:1px solid #000; + left:50% +} + +.style-transparent-choice.step0 .line { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.style-transparent-choice.step1 .line { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.style-transparent-choice.step2 .line { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.style-transparent-choice.step3 .line { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.style-transparent-choice.step4 .line { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.style-transparent-choice.step5 .line { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15); + border-left:1px solid #fff +} + +.color-primary { + color:#6d8524 +} + +.color-secondary { + color:#3f4237 +} + +.layout-columns { + display:table; + width:100%; + padding:0; + margin:0; + table-layout:fixed +} + +.layout-columns>li { + display:table-cell; + width:2%; + word-break:break-word +} + +.layout-float { + padding:0; + margin:0 +} + +.layout-float>li { + list-style:none!important; + display:block; + float:left; + text-align:center; + margin-right:15px +} + +.layout-thee-columns>li { + padding-top:17px; + padding-bottom:17px; + word-break:break-word +} + +.layout-thee-columns>li.first { + padding-right:10px +} + +.layout-thee-columns>li.third { + padding-left:10px +} + +.layout-float-left { + float:left; + margin-right:10px +} + +.layout-float-right { + float:right; + margin-left:10px +} + +.layout-fixed-width-200 { + width:200px +} + +.text-align-left { + text-align:left +} + +.text-align-center { + text-align:center +} + +.text-align-right { + text-align:right +} + +.text-antialiased { + -webkit-font-smoothing:antialiased +} + +.text-subpixel-antialiased { + -webkit-font-smoothing:subpixel-antialiased +} + +.text-size-s { + font-size:12px +} + +@media only screen and (min-width:800px) { +.text-size-s { + font-size:14px +} +} + +.text-size-m { + font-size:13px +} + +@media only screen and (min-width:800px) { +.text-size-m { + font-size:15px +} +} + +.text-size-xl { + font-size:15px +} + +@media only screen and (min-width:800px) { +.text-size-xl { + font-size:22px +} +} + +.file-upload-box { + width:249px; + height:250px +} + +.default .connected .file-upload-box { + margin-bottom:10px +} + +.file-upload-box>.box { + position:relative; + padding:1px 0; + height:100% +} + +.file-upload-box svg { + padding-top:15px; + margin:0 auto +} + +.file-upload-box svg.icon-cloud path { + fill:#3f4237 +} + +.file-upload-box svg.icon-file path { + stroke:#3f4237 +} + +.file-upload-box .box-cancel { + z-index:6; + cursor:pointer; + display:block; + position:absolute; + top:-1px; + right:1px; + width:48px; + height:50px; + background-image:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tickbg.png) +} + +.file-upload-box .box-cancel svg { + top:10px; + right:5px; + padding:0; + position:absolute; + -khtml-opacity:.65; + -moz-opacity:.65; + opacity:.65; + -ms-filter:alpha(Opacity=65) +} + +.file-upload-box .box-cancel svg path { + stroke:#3f4237 +} + +.file-upload-box .box-cancel:hover span { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +.file-upload-box input { + position:absolute; + top:0; + right:0; + bottom:0; + left:0; + z-index:5; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + cursor:pointer; + width:100%; + height:100% +} + +.file-upload-box .box-content-wrapper { + padding:0 1px; + width:247px; + height:250px; + display:table +} + +.file-upload-box .box-content-wrapper .box-content { + display:table-cell; + text-align:center; + vertical-align:middle +} + +.file-upload-box .box-content-wrapper .box-content img,.file-upload-box .box-content-wrapper .box-content svg { + max-width:247px; + max-height:200px +} + +.file-upload-box.status-uploading .overlay,.file-upload-box.status-error .overlay,.file-upload-box.status-success .overlay { + display:none!important +} + +.file-upload-box.status-uploading .box-text p,.file-upload-box.status-success .box-text p { + display:block; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + -o-text-overflow:ellipsis; + -ms-text-overflow:ellipsis; + width:215px +} + +.file-upload-box .box-text { + padding:15px; + color:#3f4237; + text-align:center; + line-height:20px; + font-size:14px +} + +.file-upload-box-simple input { + color:#3f4237 +} + +.file-upload-box-simple .box-text { + font-size:11px; + padding:10px 0; + color:#3f4237 +} + +.default .file-upload:not(.focus) input { + display:none!important +} + +.progress-bar { + position:relative; + display:block; + background-color:rgba(63,66,55,.2); + width:175px; + height:8px; + margin:15px auto 0; + border-radius:5px; + -webkit-border-radius:5px; + -moz-border-radius:5px +} + +.progress-bar .bar { + position:absolute; + display:block; + top:0; + bottom:0; + left:0; + background-color:#3f4237; + border-radius:5px; + -webkit-border-radius:5px; + -moz-border-radius:5px +} + +.choice-square { + text-align:center; + cursor:pointer +} + +.choice-square .choice-wrapper { + position:relative; + display:block; + padding:20px 0 19px +} + +.choice-square .label { + position:relative; + z-index:1; + color:#3f4237 +} + +.report .choice-square .label { + color:#fff +} + +.choice-square.preselected { + background-color:rgba(63,66,55,.15) +} + +.choice-square.checked { + -webkit-animation:select-effect 250ms 2; + -moz-animation:select-effect 250ms 2; + -ms-animation:select-effect 250ms 2 +} + +@-webkit-keyframes select-effect { +0%,100% { + opacity:1 +} + +50% { + opacity:0 +} +} + +@-moz-keyframes select-effect { +0%,100% { + opacity:1 +} + +50% { + opacity:0 +} +} + +@-ms-keyframes select-effect { +0%,100% { + opacity:1 +} + +50% { + opacity:0 +} +} + +.choice-square.checked .label { + color:rgba(255,255,255,.7) +} + +.choice-square.checked .background { + position:absolute; + z-index:0; + font-weight:700; + background-color:#3f4237; + top:1px; + bottom:1px; + left:0; + right:1px +} + +.choice-square.checked.step0 .background,.choice-square.checked.step1 .background { + top:0; + bottom:0; + left:-1px; + right:0 +} + +.choice-square .line { + display:none +} + +.choice-square.showLineConnector .line { + display:block +} + +.choice-square .level { + position:absolute; + background-color:rgba(255,255,255,.4); + bottom:0; + left:0; + right:0 +} + +.stripe-logo { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + border:1px solid #6d8524!important; + color:#6d8524!important; + text-decoration:none; + padding:4px 6px; + font-size:12px; + margin-top:20px; + line-height:19px; + opacity:.65; + border-radius:5px; + -webkit-border-radius:5px; + -moz-border-radius:5px; + height:17px +} + +.stripe-logo:hover { + background:rgba(109,133,36,.1) +} + +.stripe-logo svg { + width:40px; + height:17px +} + +.stripe-logo svg path,.stripe-logo svg rect { + fill:#6d8524 +} + +.stripe-logo>span { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + position:relative; + font-family:Helvetica,Arial; + margin-left:3px +} + +.container { + position:relative +} + +.container .aux { + border-color:#3f4237 +} + +.container .aux,.container .aux div { + position:absolute; + top:0; + bottom:0; + left:0; + right:0; + border-radius:2px; + -webkit-border-radius:2px; + -moz-border-radius:2px +} + +.container .aux img { + width:100%; + height:100% +} + +.container .dark { + display:none +} + +.container.step5 .dark { + display:block +} + +.container.step5 .light { + display:none +} + +.container.step0 .aux img { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.container.step1 .aux img { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.container.step2 .aux img { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.container.step3 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.container.step4 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.container.step5 .aux img { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + +.container .bg { + z-index:2; + -webkit-box-shadow:0 1px 1px #000; + -moz-box-shadow:0 1px 1px #000; + box-shadow:0 1px 1px #000 +} + +.container.step0 .bg { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.container.step1 .bg { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.container.step2 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.container.step3 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.container.step4 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.container.step5 .bg { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10) +} + +.container .inset { + z-index:3; + -webkit-box-shadow:inset 0 2px 0 #fff; + -moz-box-shadow:inset 0 2px 0 #fff; + box-shadow:inset 0 2px 0 #fff +} + +.container.step0 .inset { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.container.step1 .inset { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.container.step2 .inset { + -khtml-opacity:.25; + -moz-opacity:.25; + opacity:.25; + -ms-filter:alpha(Opacity=25) +} + +.container.step3 .inset { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.container.step4 .inset { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.container.step5 .inset { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.container .bd { + z-index:4; + border:1px solid #000 +} + +.container.without-padding .inset { + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0 +} + +.container.without-padding .bd { + border-left:0; + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0 +} + +.container.without-padding.first .bd { + border-left-style:solid; + border-left-width:1px +} + +.container.without-padding.first .bd,.container.without-padding.first .background,.container.without-padding.first .inset { + border-radius:2px 0 0 2px; + -webkit-border-radius:2px 0 0 2px; + -moz-border-radius:2px 0 0 2px +} + +.container.without-padding.last .bd,.container.without-padding.last .background,.container.without-padding.last .inset { + border-radius:0 2px 2px 0; + -webkit-border-radius:0 2px 2px 0; + -moz-border-radius:0 2px 2px 0 +} + +.container.step0 .bd { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.container.step1 .bd { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.container.step2 .bd { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.container.step3 .bd { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.container.step4 .bd { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.container.step5 .bd { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15); + border-color:#fff +} + +.container .tick { + display:none; + position:absolute; + right:7px; + top:50%; + margin-top:-15px; + font-size:21px; + -khtml-opacity:.7; + -moz-opacity:.7; + opacity:.7; + -ms-filter:alpha(Opacity=70) +} + +.container .tick:before { + font-family:typeIconFont; + content:"H" +} + +.container.selected .tick { + display:block +} + +.container.selected.step0 .overlay { + -khtml-opacity:.03; + -moz-opacity:.03; + opacity:.03; + -ms-filter:alpha(Opacity=3); + display:block; + background-color:#000 +} + +.container.selected.step1 .overlay { + -khtml-opacity:.1; + -moz-opacity:.1; + opacity:.1; + -ms-filter:alpha(Opacity=10); + display:block; + background-color:#fff +} + +.container.selected.step2 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.container.selected.step3 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.container.selected.step4 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.container.selected.step5 .overlay { + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5); + display:block; + background-color:#fff +} + +.container .overlay { + background-color:#000; + background-color:rgba(0,0,0,0); + z-index:1 +} + +.container.step0 .overlay { + background-color:#000; + background-color:rgba(0,0,0,0) +} + +.container.step1 .overlay,.container.step2 .overlay,.container.step3 .overlay,.container.step4 .overlay,.container.step5 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,0) +} + +.container.pre-selected.step0 .overlay,.container.active.step0 .overlay { + background-color:#000; + background-color:rgba(0,0,0,.05) +} + +.container.pre-selected.step0 .bd,.container.active.step0 .bd { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.container.pre-selected.step1 .overlay,.container.active.step1 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.2) +} + +.container.pre-selected.step1 .bd,.container.active.step1 .bd { + -khtml-opacity:.35; + -moz-opacity:.35; + opacity:.35; + -ms-filter:alpha(Opacity=35) +} + +.container.pre-selected.step2 .overlay,.container.active.step2 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.container.pre-selected.step2 .bd,.container.active.step2 .bd { + -khtml-opacity:.45; + -moz-opacity:.45; + opacity:.45; + -ms-filter:alpha(Opacity=45) +} + +.container.pre-selected.step3 .overlay,.container.active.step3 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.container.pre-selected.step3 .bd,.container.active.step3 .bd { + -khtml-opacity:.55; + -moz-opacity:.55; + opacity:.55; + -ms-filter:alpha(Opacity=55) +} + +.container.pre-selected.step4 .overlay,.container.active.step4 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.container.pre-selected.step4 .bd,.container.active.step4 .bd { + -khtml-opacity:.65; + -moz-opacity:.65; + opacity:.65; + -ms-filter:alpha(Opacity=65) +} + +.container.pre-selected.step5 .overlay,.container.active.step5 .overlay { + background-color:#fff; + background-color:rgba(255,255,255,.1) +} + +.container.pre-selected.step5 .bd,.container.active.step5 .bd { + -khtml-opacity:.25; + -moz-opacity:.25; + opacity:.25; + -ms-filter:alpha(Opacity=25) +} + +.container:hover .no-hover .overlay { + display:none +} + +.container .line { + position:absolute; + height:10px; + bottom:-10px; + width:1px; + border-left:1px solid #000; + left:50% +} + +.container.step0 .line { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15) +} + +.container.step1 .line { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.container.step2 .line { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.container.step3 .line { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.container.step4 .line { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.container.step5 .line { + -khtml-opacity:.15; + -moz-opacity:.15; + opacity:.15; + -ms-filter:alpha(Opacity=15); + border-left:1px solid #fff +} + +.preventHover .container:hover .overlay { + display:none!important +} + +.preventHover .container.pre-selected .overlay { + display:block!important +} + +.freezeframe-container { + margin:0 +} + +.ios-picture-choice-gif-layer { + opacity:0; + top:0; + left:0; + width:1px; + height:1px; + position:absolute; + z-index:4 +} + +html,body,div,applet,object,iframe,a,abbr,acronym,address,big,cite,code,del,dfn,font,img,ins,kbd,q,s,samp,small,strike,sub,sup,tt,var,dl,dt,dd,ol,ul,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,label,legend,p,blockquote,table,caption,tbody,tfoot,thead,tr,th,td { + margin:0; + padding:0; + border:0; + outline:0; + font-weight:inherit; + font-style:inherit; + font-size:100%; + font-family:inherit; + vertical-align:baseline +} + +body { + line-height:1; + color:#000; + background:#fff +} + +:focus { + outline:0 +} + +table { + border-collapse:collapse; + border-spacing:0 +} + +caption,th,td { + text-align:left; + font-weight:400 +} + +fieldset,img { + border:0 +} + +address,caption,cite,code,dfn,th,var { + font-style:normal; + font-weight:400 +} + +ol,ul { + list-style:none +} + +h1,h2,h3,h4,h5,h6 { + font-size:100%; + font-weight:400 +} + +blockquote:before,blockquote:after,q:before,q:after { + content:"" +} + +blockquote,q { + quotes:"" "" +} + +abbr,acronym { + border:0 +} + +.buttonColor { + color:#285F90 +} + +.backgroundColor { + background-color:gray; +} + +.primaryColor { + color:#3f4237 +} + +.secondaryColor { + color:#3f4237 +} + +.overlayOpacity { + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54) +} + +.overlayColor { + background-color:#fff +} + +a:not(.button) { + color:#6d8524; + text-decoration:none; + border-bottom:1px dotted #6d8524 +} + +body { + background-color:white; + overflow:hidden; + overflow-x:hidden +} + +body.scroll-disable { + overflow:hidden +} + +body.form-ready { + overflow:auto +} + +#loader { + background-color:#c1ef41 +} + +.questions,.screen .content,.footer .content,.footer-confirm .wrapper,.screen .footer .content,.footer-message .content { + display:block; + margin:0 auto; + max-width:800px +} + +.focus-current .questions>li { + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +.resizing .form,.resizing .form .questions>li,.resizing .form .questions>li .content { + display:block!important +} + +.resizing .screen { + position:fixed; + display:block!important +} + +.resizing.resizing-scroll .form .questions>li { + display:none!important +} + +.resizing.resizing-scroll .form .questions>li.active { + display:block!important +} + +body.field .footer,body.field .footer-message { + display:none +} + +body.field .form .questions { + padding-bottom:300px +} + + { + position:relative; + font-family:Arvo,Georgia,serif; + z-index:3 +} + + .form { + position:relative; + z-index:5 +} + + .form .questions { + position:relative; + font-family:Arvo,Georgia,serif; + padding:0 20px +} + +@media only screen and (min-width:800px) { + .form .questions { + padding:0 +} +} + + .form .questions>li { + position:relative; + -webkit-tap-highlight-color:rgba(0,0,0,0); + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20); + display:none +} + + .form .questions>li .wrapper { + padding:30px 20px 30px 0 +} + + .form .questions>li.active { + display:block +} + + .form .questions>li.visible .content { + display:block +} + + .form .questions>li.focus { + cursor:default; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + + .form .questions>li.focus div.item .arrow { + display:block +} + + .form .questions>li.fadeout { + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0) +} + + .form .questions>li .content { + display:none; + position:relative; + padding:37px 27px 0 50px +} + + .form .questions>li .content .description, .form .questions>li .content .stripe-description { + margin-top:-16px; + margin-bottom:33px; + font-size:16px; + -webkit-font-smoothing:antialiased; + line-height:160%; + text-align:left; + color:#6d8524; + -khtml-opacity:.8; + -moz-opacity:.8; + opacity:.8; + -ms-filter:alpha(Opacity=80) +} + + .form .questions>li .content .description .placeholder, .form .questions>li .content .stripe-description .placeholder { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.question { + color:#6d8524; + font-family:Arvo,Georgia,serif; + position:relative; + padding:0; + cursor:pointer; + padding-left:50px; + line-height:140%; + text-align:left; + font-size:24px; + -webkit-touch-callout:text; + -webkit-user-select:text; + -khtml-user-select:text; + -moz-user-select:text; + -ms-user-select:text; + user-select:text +} + +@media (max-width:800px) { +.question { + font-size:22px +} +} + +@media (max-width:550px),(max-height:450px) { +.question { + font-size:20px +} +} + +var.tag { + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + background-color:rgba(109,133,36,.3); + border:1px solid; + font-size:12px; + -webkit-font-smoothing:subpixel-antialiased; + padding:0 6px 0 7px; + vertical-align:middle; + display:block; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + -o-text-overflow:ellipsis; + -ms-text-overflow:ellipsis; + display:inline-block; + max-width:200px; + height:30px +} + +.item { + color:#3f4237; + font-family:Arvo,Georgia,serif; + -webkit-font-smoothing:antialiased; + position:absolute; + padding:0; + text-align:right; + width:20px; + line-height:35px; + margin-right:30px; + font-size:16px +} + +@media (max-width:800px) { +.item { + font-size:16px; + line-height:31px +} +} + +@media (max-width:550px),(max-height:450px) { +.item { + line-height:28px; + font-size:15px +} +} + +.sub-question .item,.header.sub .item { + width:40px; + font-size:20px +} + +.sub-question .item .arrow,.header.sub .item .arrow { + display:none!important +} + +.item .arrow { + position:absolute; + left:25px; + top:16px; + height:4px; + width:7px; + background-color:#3f4237 +} + +@media (max-width:800px) { +.item .arrow { + top:14px +} +} + +@media (max-width:550px),(max-height:450px) { +.item .arrow { + top:12px +} +} + +.item .arrow .arrow-right { + width:0; + height:0; + border-top:5px solid transparent; + border-bottom:5px solid transparent; + border-left:5px solid; + margin-left:7px; + margin-top:-3px +} + +.item .required { + display:none +} + +.focus .message { + visibility:visible +} + +.preview.safari #background,.preview.safari .footer,.preview.safari #progress,.preview.safari .confirm.container,.preview.safari .questions>li { + -webkit-transform:translate3d(0,0,0); + -moz-transform:translate3d(0,0,0); + transform:translate3d(0,0,0) +} + +.mozilla .letter { + box-shadow:none +} + +.mozilla .container .bg,.mozilla .container .inset { + display:none +} + +.ie9 .question { + margin-top:0 +} + +.ie9 input { + width:100% +} + +.screen #watermark { + z-index:12 +} + +#watermark { + position:fixed; + top:0; + right:0; + padding:15px; + z-index:4; + font-family:Helvetica,Arial +} + +#watermark a { + font-size:13px; + text-decoration:none; + color:#fff; + color:rgba(255,255,255,.3) +} + +#watermark a:hover { + color:#fff +} + +.dark #watermark a { + color:#000; + color:rgba(0,0,0,.3) +} + +.dark #watermark a:hover { + color:#000 +} + +.asterisk { + font-family:'Courier New',monospace; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + margin-top:-5px +} + +.credits { + color:#6d8524; + height:14px; + font-family:Helvetica,Arial; + opacity:.8; + font-size:12px +} + +.credits a { + color:#6d8524; + text-decoration:none; + font-weight:700; + border-bottom:1px solid #6d8524 +} + +.credits a:hover { + border-bottom:2px solid #6d8524 +} + +#errors,#texts { + display:none +} + +#load { + -khtml-opacity:.01; + -moz-opacity:.01; + opacity:.01; + -ms-filter:alpha(Opacity=1); + height:1px; + width:1px +} + +#load:before { + font-family:typeIconFont; + content:"H" +} + +.fullscreen #background { + background-size:cover +} + +.repeat #background { + background-repeat:repeat +} + +.no-repeat #background { + background-repeat:no-repeat +} + +.persistent.overlayColor { + position:absolute; + height:100%; + width:100%; + top:0; + left:0; + z-index:1 +} + +.persistent.background { + position:absolute; + bottom:0; + left:0; + right:0 +} + +#background { + position:fixed; + background-color:#c1ef41; + background-image:url(https://d4z6dx8qrln4r.cloudfront.net/background-5406f83068c19-default.jpeg); + width:100%; + height:100%; + z-index:1; + top:0 +} + +#background div { + position:absolute; + height:100%; + width:100%; + top:0; + background-color:#fff; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54) +} + +#private { + position:fixed; + top:0; + display:block; + background-color:#000; + font-family:Helvetica,Arial; + text-align:center; + height:32px; + z-index:12; + line-height:20px; + width:100%; + margin:0 auto; + border-radius:4px; + -webkit-border-radius:4px; + -moz-border-radius:4px; + margin-top:-4px; + padding-top:15px; + color:#fff; + font-size:16px +} + +#private.old-plans { + color:#faffcf; + font-size:12px; + padding-top:4px; + height:20px +} + +#private.old-plans img { + right:8px; + top:8px +} + +#private img { + width:10px; + height:10px; + position:absolute; + right:15px; + top:19px; + cursor:pointer +} + +#private span { + display:none +} + +#private.top span.top { + display:block +} + +#private.embed span.embed { + display:block +} + +.empty-form { + position:fixed; + top:50%; + z-index:3; + width:100%; + padding:10px; + font-size:18px; + line-height:20px; + text-align:center; + margin-top:-10px +} + +.header { + position:fixed; + width:100%; + z-index:6; + border-bottom-style:solid; + border-bottom-width:1px; + overflow:hidden; + -moz-transition:top ease-out 150ms 0; + -webkit-transition:top ease-out 150ms 0; + -o-transition:top ease-out 150ms 0; + transition:top ease-out 150ms 0 +} + +.header.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +.header.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +.header.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +.header.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +.header.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +.header.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.header.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +.header.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.header.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +.header.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +.header.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +.header.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.header.sub { + z-index:5; + top:98px; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + visibility:hidden; + -moz-transition:opacity ease-out 150ms 0; + -webkit-transition:opacity ease-out 150ms 0; + -o-transition:opacity ease-out 150ms 0; + transition:opacity ease-out 150ms 0 +} + +.header.sub.show { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + visibility:visible; + -moz-transition:none ease-out 0 0; + -webkit-transition:none ease-out 0 0; + -o-transition:none ease-out 0 0; + transition:none ease-out 0 0 +} + +.header .arrow { + display:block +} + +.header .description { + display:none; + visibility:hidden +} + +.header .question-wrap { + position:relative +} + +.header .question-wrap .item { + top:0 +} + +.header .question .attachment { + float:right; + padding-left:20px +} + +.header .question .attachment img { + max-height:78px +} + +.header .question .stripe-logo { + margin-top:0; + float:right; + margin-left:10px +} + +.header .attachment { + display:none +} + +.header .close { + display:none; + position:fixed; + top:10px; + right:30px; + width:30px; + height:30px; + font-size:55px; + color:rgba(109,133,36,.8); + font-family:HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif; + font-weight:200; + cursor:pointer +} + +.header .close:hover { + color:#6d8524 +} + +.header.type-default .question { + display:block; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + -o-text-overflow:ellipsis; + -ms-text-overflow:ellipsis +} + +.header.type-default:hover { + height:auto!important +} + +.header.type-default:hover .question { + display:block; + white-space:normal; + overflow:auto; + text-overflow:clip; + -o-text-overflow:clip; + -ms-text-overflow:clip +} + +.header.type-group { + -moz-transition:all ease-out 150ms 0; + -webkit-transition:all ease-out 150ms 0; + -o-transition:all ease-out 150ms 0; + transition:all ease-out 150ms 0; + height:100px +} + +.header.type-group.openable:hover { + padding-bottom:7px +} + +.header.type-group .question-wrap { + padding:0 50px 0 0 +} + +.header.type-group .question-wrap .question { + display:block; + width:100%; + font-size:20px; + line-height:128%; + float:left +} + +.header.type-group .question-wrap .item { + top:-5px +} + +.header.type-group .full-content { + padding:0 50px +} + +.header.type-group .full-content .description { + visibility:hidden; + display:none; + padding:15px 0; + font-size:19px; + line-height:160%; + text-align:left; + color:#6d8524; + -khtml-opacity:.8; + -moz-opacity:.8; + opacity:.8; + -ms-filter:alpha(Opacity=80) +} + +@media (max-width:800px) { +.header.type-group .full-content .description { + font-size:17px +} +} + +@media (max-width:550px),(max-height:450px) { +.header.type-group .full-content .description { + font-size:16px +} +} + +.header.type-group.one-lines .question-wrap { + top:25px +} + +.header.type-group.one-lines .question .attachment { + margin-top:-25px +} + +.header.type-group.two-lines .question-wrap { + top:12px +} + +.header.type-group.two-lines .question .attachment { + margin-top:-10px +} + +.header.type-group.three-lines .question-wrap { + top:0 +} + +.header.type-group.three-lines .question .attachment { + margin-top:0 +} + +.header.type-group.n-lines .question-wrap { + top:0 +} + +.header.type-group.n-lines .smooth-break { + display:block +} + +.header.type-group .smooth-break { + position:absolute; + display:none; + left:0; + bottom:0; + right:0; + height:10px; + z-index:5; + background-repeat:repeat-x; + background-image:-khtml-gradient(linear,left top,left bottom,from(rgba(187,237,46,0)),to(#bbed2e)); + background-image:-moz-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-ms-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(187,237,46,0)),color-stop(100%,#bbed2e)); + background-image:-webkit-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-o-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:linear-gradient(rgba(187,237,46,0),#bbed2e) +} + +.header.thumbnail-show .question .attachment { + display:block +} + +.header.state-full .question-wrap { + top:0!important +} + +.header.state-full .question .attachment { + display:none +} + +.header.state-full .content { + overflow:scroll +} + +.header.state-full .content .content-wrapper { + margin-bottom:30px +} + +.header.state-full .full-content .attachment { + padding:15px 0; + display:block +} + +.header.state-full .full-content .description { + visibility:visible +} + +.header.state-full .close { + display:block +} + +.header .background { + position:absolute; + background:#c1ef41; + top:0; + left:0; + right:0; + bottom:0; + z-index:1 +} + +.header .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:3; + background-color:#fff; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54) +} + +.header .content { + z-index:5; + padding:11px 20px; + position:relative; + height:100% +} + +.header .content .question { + max-width:715px; + margin:0 +} + +.header .content .content-wrapper { + display:block; + margin:0 auto; + max-width:800px +} + +.setHeaderGradient { + background-repeat:repeat-x; + background-image:-khtml-gradient(linear,left top,left bottom,from(rgba(187,237,46,0)),to(#bbed2e)); + background-image:-moz-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-ms-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(187,237,46,0)),color-stop(100%,#bbed2e)); + background-image:-webkit-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:-o-linear-gradient(rgba(187,237,46,0),#bbed2e); + background-image:linear-gradient(rgba(187,237,46,0),#bbed2e) +} + +.footer-error.confirm-nav .footer-message { + display:none +} + +#fixed-footer { + z-index:11; + position:fixed; + bottom:0; + width:100%; + border-top-style:solid; + border-top-width:1px; + height:60px; + -webkit-touch-callout:none; + -webkit-user-select:none; + -khtml-user-select:none; + -moz-user-select:-moz-none; + -ms-user-select:none; + user-select:none +} + +#fixed-footer.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +#fixed-footer.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +#fixed-footer.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +#fixed-footer.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +#fixed-footer.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +#fixed-footer.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#fixed-footer.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +#fixed-footer.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#fixed-footer.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +#fixed-footer.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +#fixed-footer.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +#fixed-footer.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.field #fixed-footer { + display:none +} + +#fixed-footer>.background { + background-color:#c1ef41; + top:0; + z-index:2 +} + +#fixed-footer .footer-message { + position:absolute; + width:100%; + top:0; + z-index:1; + -moz-transition:top ease-out 100ms 0; + -webkit-transition:top ease-out 100ms 0; + -o-transition:top ease-out 100ms 0; + transition:top ease-out 100ms 0 +} + +.footer-error #fixed-footer .footer-message { + -moz-transition:top ease-out 100ms 0; + -webkit-transition:top ease-out 100ms 0; + -o-transition:top ease-out 100ms 0; + transition:top ease-out 100ms 0; + top:-23px +} + +#fixed-footer .footer-message .content { + padding:5px +} + +#fixed-footer .footer-message .content span { + font-family:Helvetica,Arial; + position:relative; + color:#fff; + font-size:13px; + z-index:2; + line-height:13px; + left:10px +} + +@media only screen and (min-width:800px) { +#fixed-footer .footer-message .content span { + left:54px +} +} + +#fixed-footer .footer-message .background { + position:absolute; + left:0; + top:0; + right:0; + bottom:0; + background-color:#900; + z-index:1 +} + +#fixed-footer .by { + position:absolute; + right:115px; + top:17px; + z-index:5; + display:none +} + +@media only screen and (min-width:500px) { +#fixed-footer .by { + display:block +} +} + +#fixed-footer .by .button { + text-decoration:none; + font-family:Helvetica,Arial; + font-size:11px; + padding:3px 6px; + height:16px; + line-height:17px; + font-weight:400; + cursor:pointer +} + +#fixed-footer .by .button:active { + line-height:18px!important +} + +#fixed-footer.review .content .button-wrapper.review { + display:block +} + +#fixed-footer.review .content .button-wrapper.submit { + display:none +} + +#fixed-footer .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:3; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54); + background-color:#fff +} + +#fixed-footer .button-text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237; + top:6px; + position:absolute; + left:100%; + width:150px; + text-align:left +} + +#fixed-footer .nav-buttons { + position:absolute; + z-index:99; + right:8px; + top:8px +} + +#fixed-footer .nav-buttons .button-wrapper { + float:left; + margin-left:6px +} + +#fixed-footer .content { + position:relative; + z-index:4; + margin:0 auto; + max-width:800px +} + +#fixed-footer .content .text { + color:#3f4237 +} + +#fixed-footer .content .button-wrapper { + position:absolute +} + +#fixed-footer .content .button-wrapper.confirm { + top:8px; + left:0 +} + +#fixed-footer .content .button-wrapper.submit,#fixed-footer .content .button-wrapper.review { + top:20px; + left:20px +} + +@media only screen and (min-width:800px) { +#fixed-footer .content .button-wrapper.submit,#fixed-footer .content .button-wrapper.review { + top:20px; + left:45px +} +} + +#fixed-footer .content .button-wrapper.review { + display:none +} + +#fixed-footer .content .phishing-notice { + top:73px; + left:10px; + position:absolute; + opacity:.8; + font-size:13px +} + +@media only screen and (min-width:800px) { +#fixed-footer .content .phishing-notice { + top:89px; + left:45px +} +} + +@media (max-width:800px) { +#fixed-footer .content .phishing-notice { + left:20px; + top:83px +} +} + +@media (max-width:550px),(max-height:450px) { +#fixed-footer .content .phishing-notice { + left:20px; + top:83px +} +} + +#fixed-footer .content .phishing-notice a { + color:#3f4237; + text-decoration:underline; + border-bottom:0 +} + +#fixed-footer .content #progress { + top:0; + margin-top:14px; + position:absolute; + width:324px; + left:10px; + visibility:hidden; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -moz-transition:opacity ease-out 300ms 0; + -webkit-transition:opacity ease-out 300ms 0; + -o-transition:opacity ease-out 300ms 0; + transition:opacity ease-out 300ms 0 +} + +.proportion #fixed-footer .content #progress,.percent #fixed-footer .content #progress { + visibility:visible; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +@media only screen and (min-width:800px) { +#fixed-footer .content #progress { + left:55px +} +} + +#fixed-footer .content #progress.show { + visibility:visible; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + -moz-transition:opacity ease-out 300ms 0; + -webkit-transition:opacity ease-out 300ms 0; + -o-transition:opacity ease-out 300ms 0; + transition:opacity ease-out 300ms 0 +} + +#fixed-footer .content #progress .label { + font-size:13px; + font-family:Helvetica,Arial; + color:#3f4237 +} + +#fixed-footer .content #progress .bar { + position:relative; + margin:8px 0; + width:225px; + height:6px; + display:block +} + +#fixed-footer .content #progress .bar div { + position:absolute; + left:0; + top:0; + bottom:0 +} + +#fixed-footer .content #progress .bar div.wrapper { + right:0; + z-index:1; + -khtml-opacity:.35; + -moz-opacity:.35; + opacity:.35; + -ms-filter:alpha(Opacity=35); + border-radius:10px; + -webkit-border-radius:10px; + -moz-border-radius:10px; + background-color:#3f4237 +} + +#fixed-footer .content #progress .bar div.progress { + display:block; + width:0; + z-index:2; + height:100%; + -moz-transition:width ease-out 200ms 0; + -webkit-transition:width ease-out 200ms 0; + -o-transition:width ease-out 200ms 0; + transition:width ease-out 200ms 0; + background-color:#3f4237; + -webkit-border-top-left-radius:10px; + -webkit-border-top-right-radius:0; + -webkit-border-bottom-right-radius:0; + -webkit-border-bottom-left-radius:10px; + -moz-border-radius-topleft:10px; + -moz-border-radius-topright:0; + -moz-border-radius-bottomright:0; + -moz-border-radius-bottomleft:10px; + border-top-left-radius:10px; + border-top-right-radius:0; + border-bottom-right-radius:0; + border-bottom-left-radius:10px +} + +#fixed-footer .content #progress .bar div.progress.rounded { + -webkit-border-top-left-radius:10px; + -webkit-border-top-right-radius:10px; + -webkit-border-bottom-right-radius:10px; + -webkit-border-bottom-left-radius:10px; + -moz-border-radius-topleft:10px; + -moz-border-radius-topright:10px; + -moz-border-radius-bottomright:10px; + -moz-border-radius-bottomleft:10px; + border-top-left-radius:10px; + border-top-right-radius:10px; + border-bottom-right-radius:10px; + border-bottom-left-radius:10px +} + +#unfixed-footer { + z-index:10; + position:fixed; + bottom:0; + width:100%; + border-top-style:solid; + border-top-width:1px; + height:60px; + -webkit-touch-callout:none; + -webkit-user-select:none; + -khtml-user-select:none; + -moz-user-select:-moz-none; + -ms-user-select:none; + user-select:none; + -webkit-box-shadow:none!important; + -moz-box-shadow:none!important; + box-shadow:none!important +} + +#unfixed-footer.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +#unfixed-footer.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +#unfixed-footer.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +#unfixed-footer.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +#unfixed-footer.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +#unfixed-footer.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#unfixed-footer.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +#unfixed-footer.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#unfixed-footer.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +#unfixed-footer.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +#unfixed-footer.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +#unfixed-footer.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.field #unfixed-footer { + display:none +} + +#unfixed-footer>.background { + background-color:#c1ef41; + top:0; + z-index:2 +} + +#unfixed-footer .footer-message { + position:absolute; + width:100%; + top:0; + z-index:1; + -moz-transition:top ease-out 100ms 0; + -webkit-transition:top ease-out 100ms 0; + -o-transition:top ease-out 100ms 0; + transition:top ease-out 100ms 0 +} + +.footer-error #unfixed-footer .footer-message { + -moz-transition:top ease-out 100ms 0; + -webkit-transition:top ease-out 100ms 0; + -o-transition:top ease-out 100ms 0; + transition:top ease-out 100ms 0; + top:-23px +} + +#unfixed-footer .footer-message .content { + padding:5px +} + +#unfixed-footer .footer-message .content span { + font-family:Helvetica,Arial; + position:relative; + color:#fff; + font-size:13px; + z-index:2; + line-height:13px; + left:10px +} + +@media only screen and (min-width:800px) { +#unfixed-footer .footer-message .content span { + left:54px +} +} + +#unfixed-footer .footer-message .background { + position:absolute; + left:0; + top:0; + right:0; + bottom:0; + background-color:#900; + z-index:1 +} + +#unfixed-footer .by { + position:absolute; + right:115px; + top:17px; + z-index:5; + display:none +} + +@media only screen and (min-width:500px) { +#unfixed-footer .by { + display:block +} +} + +#unfixed-footer .by .button { + text-decoration:none; + font-family:Helvetica,Arial; + font-size:11px; + padding:3px 6px; + height:16px; + line-height:17px; + font-weight:400; + cursor:pointer +} + +#unfixed-footer .by .button:active { + line-height:18px!important +} + +#unfixed-footer.review .content .button-wrapper.review { + display:block +} + +#unfixed-footer.review .content .button-wrapper.submit { + display:none +} + +#unfixed-footer .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:3; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54); + background-color:#fff +} + +#unfixed-footer .button-text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237; + top:6px; + position:absolute; + left:100%; + width:150px; + text-align:left +} + +#unfixed-footer .nav-buttons { + position:absolute; + z-index:99; + right:8px; + top:8px +} + +#unfixed-footer .nav-buttons .button-wrapper { + float:left; + margin-left:6px +} + +#unfixed-footer .content { + position:relative; + z-index:4; + margin:0 auto; + max-width:800px +} + +#unfixed-footer .content .text { + color:#3f4237 +} + +#unfixed-footer .content .button-wrapper { + position:absolute +} + +#unfixed-footer .content .button-wrapper.confirm { + top:8px; + left:0 +} + +#unfixed-footer .content .button-wrapper.submit,#unfixed-footer .content .button-wrapper.review { + top:20px; + left:20px +} + +@media only screen and (min-width:800px) { +#unfixed-footer .content .button-wrapper.submit,#unfixed-footer .content .button-wrapper.review { + top:20px; + left:45px +} +} + +#unfixed-footer .content .button-wrapper.review { + display:none +} + +#unfixed-footer .content .phishing-notice { + top:73px; + left:10px; + position:absolute; + opacity:.8; + font-size:13px +} + +@media only screen and (min-width:800px) { +#unfixed-footer .content .phishing-notice { + top:89px; + left:45px +} +} + +@media (max-width:800px) { +#unfixed-footer .content .phishing-notice { + left:20px; + top:83px +} +} + +@media (max-width:550px),(max-height:450px) { +#unfixed-footer .content .phishing-notice { + left:20px; + top:83px +} +} + +#unfixed-footer .content .phishing-notice a { + color:#3f4237; + text-decoration:underline; + border-bottom:0 +} + +#unfixed-footer .content #progress { + top:0; + margin-top:14px; + position:absolute; + width:324px; + left:10px; + visibility:hidden; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -moz-transition:opacity ease-out 300ms 0; + -webkit-transition:opacity ease-out 300ms 0; + -o-transition:opacity ease-out 300ms 0; + transition:opacity ease-out 300ms 0 +} + +.proportion #unfixed-footer .content #progress,.percent #unfixed-footer .content #progress { + visibility:visible; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +@media only screen and (min-width:800px) { +#unfixed-footer .content #progress { + left:55px +} +} + +#unfixed-footer .content #progress.show { + visibility:visible; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + -moz-transition:opacity ease-out 300ms 0; + -webkit-transition:opacity ease-out 300ms 0; + -o-transition:opacity ease-out 300ms 0; + transition:opacity ease-out 300ms 0 +} + +#unfixed-footer .content #progress .label { + font-size:13px; + font-family:Helvetica,Arial; + color:#3f4237 +} + +#unfixed-footer .content #progress .bar { + position:relative; + margin:8px 0; + width:225px; + height:6px; + display:block +} + +#unfixed-footer .content #progress .bar div { + position:absolute; + left:0; + top:0; + bottom:0 +} + +#unfixed-footer .content #progress .bar div.wrapper { + right:0; + z-index:1; + -khtml-opacity:.35; + -moz-opacity:.35; + opacity:.35; + -ms-filter:alpha(Opacity=35); + border-radius:10px; + -webkit-border-radius:10px; + -moz-border-radius:10px; + background-color:#3f4237 +} + +#unfixed-footer .content #progress .bar div.progress { + display:block; + width:0; + z-index:2; + height:100%; + -moz-transition:width ease-out 200ms 0; + -webkit-transition:width ease-out 200ms 0; + -o-transition:width ease-out 200ms 0; + transition:width ease-out 200ms 0; + background-color:#3f4237; + -webkit-border-top-left-radius:10px; + -webkit-border-top-right-radius:0; + -webkit-border-bottom-right-radius:0; + -webkit-border-bottom-left-radius:10px; + -moz-border-radius-topleft:10px; + -moz-border-radius-topright:0; + -moz-border-radius-bottomright:0; + -moz-border-radius-bottomleft:10px; + border-top-left-radius:10px; + border-top-right-radius:0; + border-bottom-right-radius:0; + border-bottom-left-radius:10px +} + +#unfixed-footer .content #progress .bar div.progress.rounded { + -webkit-border-top-left-radius:10px; + -webkit-border-top-right-radius:10px; + -webkit-border-bottom-right-radius:10px; + -webkit-border-bottom-left-radius:10px; + -moz-border-radius-topleft:10px; + -moz-border-radius-topright:10px; + -moz-border-radius-bottomright:10px; + -moz-border-radius-bottomleft:10px; + border-top-left-radius:10px; + border-top-right-radius:10px; + border-bottom-right-radius:10px; + border-bottom-left-radius:10px +} + +.scroll-overlay { + position:fixed; + top:0; + bottom:0; + left:0; + right:0; + background:#8dba10; + display:none; + z-index:4 +} + +#banner { + position:fixed; + bottom:-60px; + width:100%; + height:60px; + z-index:5; + overflow:hidden; + border-top-style:solid; + border-top-width:1px; + -webkit-touch-callout:none; + -webkit-user-select:none; + -khtml-user-select:none; + -moz-user-select:-moz-none; + -ms-user-select:none; + user-select:none; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -moz-transition:all ease-out 300ms 0; + -webkit-transition:all ease-out 300ms 0; + -o-transition:all ease-out 300ms 0; + transition:all ease-out 300ms 0 +} + +#banner.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +#banner.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +#banner.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +#banner.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +#banner.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +#banner.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#banner.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +#banner.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +#banner.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +#banner.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +#banner.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +#banner.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.field #banner { + -moz-transition:none ease-out 0 0; + -webkit-transition:none ease-out 0 0; + -o-transition:none ease-out 0 0; + transition:none ease-out 0 0 +} + +.resizing #banner { + display:none +} + +.banner-top #banner { + box-shadow:none; + border-bottom-style:solid; + border-bottom-width:1px; + top:0; + height:40px +} + +.banner-top #banner .by { + top:8px; + right:12px +} + +#banner.risen { + bottom:0; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +#banner.risen .content .by span,#banner.risen .content .by .button { + bottom:0 +} + +#banner .background { + z-index:1; + height:100%; + background-color:#c1ef41 +} + +#banner .by { + position:absolute; + right:20px; + top:17px; + z-index:2; + display:block +} + +#banner .by span { + display:block; + float:left; + line-height:25px; + margin-right:10px; + font-family:Arial,sans-serif; + font-size:14px; + color:#3f4237; + position:relative; + bottom:-70px; + -moz-transition:all ease-out 300ms 0; + -webkit-transition:all ease-out 300ms 0; + -o-transition:all ease-out 300ms 0; + transition:all ease-out 300ms 0 +} + +#banner .by .button { + text-decoration:none; + font-family:Helvetica,Arial; + font-size:13px; + padding:3px 6px; + height:17px; + line-height:17px; + font-weight:400; + position:relative; + bottom:-80px; + -moz-transition:all ease-out 350ms 0; + -webkit-transition:all ease-out 350ms 0; + -o-transition:all ease-out 350ms 0; + transition:all ease-out 350ms 0 +} + +#banner .by .button:active { + line-height:18px!important +} + +#banner .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:2; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54); + background-color:#fff +} + +::-webkit-input-placeholder { + color:rgba(63,66,55,.4) +} + +:-moz-placeholder { + color:rgba(63,66,55,.4) +} + +::-moz-placeholder { + color:rgba(63,66,55,.4) +} + +:-ms-input-placeholder { + color:rgba(63,66,55,.4) +} + +.content-wrapper>.text,.button-wrap>.text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237 +} + +textarea.placeholder,input.placeholder { + -khtml-opacity:.3; + -moz-opacity:.3; + opacity:.3; + -ms-filter:alpha(Opacity=30) +} + +.confirm-nav.confirm-footer .footer-confirm { + visibility:visible; + height:115px +} + +.footer-confirm { + position:fixed; + left:0; + right:0; + bottom:0; + -moz-transition:all ease-out 200ms 0; + -webkit-transition:all ease-out 200ms 0; + -o-transition:all ease-out 200ms 0; + transition:all ease-out 200ms 0; + z-index:4; + height:60px; + width:100%; + overflow:hidden; + border-top-style:solid; + border-top-width:1px; + -webkit-box-shadow:none!important; + -moz-box-shadow:none!important; + box-shadow:none!important; + -webkit-touch-callout:none; + -webkit-user-select:none; + -khtml-user-select:none; + -moz-user-select:-moz-none; + -ms-user-select:none; + user-select:none; + visibility:hidden +} + +.footer-confirm.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +.footer-confirm.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +.footer-confirm.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +.footer-confirm.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +.footer-confirm.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +.footer-confirm.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.footer-confirm.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +.footer-confirm.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.footer-confirm.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +.footer-confirm.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +.footer-confirm.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +.footer-confirm.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.footer-confirm .content { + position:relative; + z-index:2; + padding:0 55px +} + +.footer-confirm .content .button-wrapper { + position:relative; + left:0 +} + +.footer-confirm .content .text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237; + padding-top:6px; + font-size:12px +} + +.footer-confirm.show { + visibility:visible +} + +.footer-confirm .button-wrapper.confirm { + z-index:3; + top:6px +} + +.footer-confirm .background { + z-index:1; + top:0; + background-color:#c1ef41 +} + +.footer-confirm .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:2; + -khtml-opacity:.54; + -moz-opacity:.54; + opacity:.54; + -ms-filter:alpha(Opacity=54); + background-color:#fff +} + +.footer-confirm .content .text .default { + display:block +} + +.footer-confirm .content .text .multiple { + display:none +} + +.footer-confirm .content .text .textarea { + display:none +} + +.confirm-text-default .footer-confirm .content .text .default { + display:block +} + +.confirm-text-default .footer-confirm .content .text .multiple { + display:none +} + +.confirm-text-default .footer-confirm .content .text .textarea { + display:none +} + +.confirm-text-textarea .footer-confirm .content .text .textarea { + display:block +} + +.confirm-text-textarea .footer-confirm .content .text .default { + display:none +} + +.confirm-text-textarea .footer-confirm .content .text .multiple { + display:none +} + +.confirm-text-multiple .footer-confirm .content .text .multiple { + display:block +} + +.confirm-text-multiple .footer-confirm .content .text .default { + display:none +} + +.confirm-text-multiple .footer-confirm .content .text .textarea { + display:none +} + +.button-wrapper { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline +} + +.button-wrapper.loading #spin { + left:50%; + text-align:center; + position:absolute; + top:50%; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button-wrapper.loading .button>span { + visibility:hidden +} + +.button.nav { + display:inline-block; + cursor:default; + background-color:#c5f044; + width:auto; + height:35px; + line-height:30px; + padding:5px 12px 0; + font-size:19px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#5c780a; + border-top:1px solid #b6ec15; + border-left:1px solid #8cb50f; + border-right:1px solid #8cb50f; + border-bottom:1px solid #607d0a; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(255,255,255,.4) 0 1px 1px +} + +.button.nav.enabled { + cursor:pointer +} + +.button.nav.hover-effect:hover.enabled,.default .focus .button.nav:hover.enabled,.default .footer-confirm .button.nav:hover.enabled,.screen .focus .button.nav:hover.enabled,.default #banner .button.nav:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#d4f473; + cursor:pointer +} + +.default .focus .button.nav:active.enabled,.default .footer .button.nav:active.enabled,.button.nav.selected,.button.nav.active.enabled,.default #banner .button.nav:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:32px; + background-color:#d4f473; + border:0; + border-bottom:1px solid #93bf0f; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.nav:active.enabled span,.default .footer .button.nav:active.enabled span,.button.nav.selected span,.button.nav.active.enabled span,.default #banner .button.nav:active.enabled span { + margin-top:1px +} + +.button.nav.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.nav .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:30px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.nav .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.nav .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.nav .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.nav .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.nav .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.nav .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.nav .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.nav .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.nav .down:before { + font-family:typeIconFont; + content:"L" +} + +.button.nav.red { + display:inline-block; + cursor:default; + background-color:#900; + width:auto; + height:35px; + line-height:30px; + padding:5px 12px 0; + font-size:19px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#ff8080; + border-top:1px solid #900; + border-left:1px solid #4d0000; + border-right:1px solid #4d0000; + border-bottom:1px solid #000; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(0,0,0,.4) 0 -1px 1px +} + +.button.nav.red.enabled { + cursor:pointer +} + +.button.nav.red.hover-effect:hover.enabled,.default .focus .button.nav.red:hover.enabled,.default .footer-confirm .button.nav.red:hover.enabled,.screen .focus .button.nav.red:hover.enabled,.default #banner .button.nav.red:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#c00; + cursor:pointer +} + +.default .focus .button.nav.red:active.enabled,.default .footer .button.nav.red:active.enabled,.button.nav.red.selected,.button.nav.red.active.enabled,.default #banner .button.nav.red:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:32px; + background-color:#c00; + border:0; + border-bottom:1px solid #300; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.nav.red:active.enabled span,.default .footer .button.nav.red:active.enabled span,.button.nav.red.selected span,.button.nav.red.active.enabled span,.default #banner .button.nav.red:active.enabled span { + margin-top:1px +} + +.button.nav.red.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.nav.red .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:30px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.nav.red .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.nav.red .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.nav.red .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.nav.red .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.nav.red .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.nav.red .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.nav.red .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.nav.red .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.nav.red .down:before { + font-family:typeIconFont; + content:"L" +} + +.button.key { + display:inline-block; + cursor:default; + background-color:#c5f044; + width:15px; + height:25px; + line-height:19px; + padding:5px 7px 0; + font-size:18px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#5c780a; + border-top:1px solid #b6ec15; + border-left:1px solid #8cb50f; + border-right:1px solid #8cb50f; + border-bottom:1px solid #607d0a; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(255,255,255,.4) 0 1px 1px +} + +.button.key.enabled { + cursor:pointer +} + +.button.key.hover-effect:hover.enabled,.default .focus .button.key:hover.enabled,.default .footer-confirm .button.key:hover.enabled,.screen .focus .button.key:hover.enabled,.default #banner .button.key:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#d4f473; + cursor:pointer +} + +.default .focus .button.key:active.enabled,.default .footer .button.key:active.enabled,.button.key.selected,.button.key.active.enabled,.default #banner .button.key:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:21px; + background-color:#d4f473; + border:0; + border-bottom:1px solid #93bf0f; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.key:active.enabled span,.default .footer .button.key:active.enabled span,.button.key.selected span,.button.key.active.enabled span,.default #banner .button.key:active.enabled span { + margin-top:1px +} + +.button.key.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.key .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:20px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.key .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.key .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.key .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.key .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.key .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.key .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.key .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.key .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.key .down:before { + font-family:typeIconFont; + content:"L" +} + +.button.key-great { + display:inline-block; + cursor:default; + background-color:#c5f044; + width:26px; + height:35px; + line-height:30px; + padding:5px 7px 0; + font-size:25px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#5c780a; + border-top:1px solid #b6ec15; + border-left:1px solid #8cb50f; + border-right:1px solid #8cb50f; + border-bottom:1px solid #607d0a; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(255,255,255,.4) 0 1px 1px +} + +.button.key-great.enabled { + cursor:pointer +} + +.button.key-great.hover-effect:hover.enabled,.default .focus .button.key-great:hover.enabled,.default .footer-confirm .button.key-great:hover.enabled,.screen .focus .button.key-great:hover.enabled,.default #banner .button.key-great:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#d4f473; + cursor:pointer +} + +.default .focus .button.key-great:active.enabled,.default .footer .button.key-great:active.enabled,.button.key-great.selected,.button.key-great.active.enabled,.default #banner .button.key-great:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:32px; + background-color:#d4f473; + border:0; + border-bottom:1px solid #93bf0f; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.key-great:active.enabled span,.default .footer .button.key-great:active.enabled span,.button.key-great.selected span,.button.key-great.active.enabled span,.default #banner .button.key-great:active.enabled span { + margin-top:1px +} + +.button.key-great.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.key-great .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:30px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.key-great .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.key-great .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.key-great .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.key-great .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.key-great .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.key-great .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.key-great .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.key-great .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.key-great .down:before { + font-family:typeIconFont; + content:"L" +} + +.button.general { + display:inline-block; + cursor:default; + background-color:#c5f044; + width:auto; + height:45px; + line-height:38px; + padding:5px 20px 0; + font-size:25px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#5c780a; + border-top:1px solid #b6ec15; + border-left:1px solid #8cb50f; + border-right:1px solid #8cb50f; + border-bottom:1px solid #607d0a; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(255,255,255,.4) 0 1px 1px +} + +.button.general.enabled { + cursor:pointer +} + +.button.general.hover-effect:hover.enabled,.default .focus .button.general:hover.enabled,.default .footer-confirm .button.general:hover.enabled,.screen .focus .button.general:hover.enabled,.default #banner .button.general:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#d4f473; + cursor:pointer +} + +.default .focus .button.general:active.enabled,.default .footer .button.general:active.enabled,.button.general.selected,.button.general.active.enabled,.default #banner .button.general:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:40px; + background-color:#d4f473; + border:0; + border-bottom:1px solid #93bf0f; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.general:active.enabled span,.default .footer .button.general:active.enabled span,.button.general.selected span,.button.general.active.enabled span,.default #banner .button.general:active.enabled span { + margin-top:1px +} + +.button.general.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.general .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:40px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.general .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.general .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.general .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.general .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.general .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.general .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.general .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.general .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.general .down:before { + font-family:typeIconFont; + content:"L" +} + +.button.general.red { + display:inline-block; + cursor:default; + background-color:#900; + width:auto; + height:45px; + line-height:38px; + padding:5px 20px 0; + font-size:25px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + font-weight:700; + font-family:Arvo,Georgia,serif; + max-width:610px; + overflow:hidden; + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + color:#ff8080; + border-top:1px solid #900; + border-left:1px solid #4d0000; + border-right:1px solid #4d0000; + border-bottom:1px solid #000; + box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35); + box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 2px 2px rgba(0,0,0,.05),transparent 0 0 0,transparent 0 0 0,transparent 0 0 0; + text-shadow:rgba(0,0,0,.4) 0 -1px 1px +} + +.button.general.red.enabled { + cursor:pointer +} + +.button.general.red.hover-effect:hover.enabled,.default .focus .button.general.red:hover.enabled,.default .footer-confirm .button.general.red:hover.enabled,.screen .focus .button.general.red:hover.enabled,.default #banner .button.general.red:hover.enabled { + -moz-transition:background-color ease-out 100ms 0; + -webkit-transition:background-color ease-out 100ms 0; + -o-transition:background-color ease-out 100ms 0; + transition:background-color ease-out 100ms 0; + background-color:#c00; + cursor:pointer +} + +.default .focus .button.general.red:active.enabled,.default .footer .button.general.red:active.enabled,.button.general.red.selected,.button.general.red.active.enabled,.default #banner .button.general.red:active.enabled { + -webkit-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + -moz-box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + box-shadow:inset 0 2px 2px rgba(0,0,0,.8); + line-height:40px; + background-color:#c00; + border:0; + border-bottom:1px solid #300; + margin:1px; + margin-bottom:0 +} + +.default .focus .button.general.red:active.enabled span,.default .footer .button.general.red:active.enabled span,.button.general.red.selected span,.button.general.red.active.enabled span,.default #banner .button.general.red:active.enabled span { + margin-top:1px +} + +.button.general.red.disabled { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50); + -moz-transition:all ease-out 0 0; + -webkit-transition:all ease-out 0 0; + -o-transition:all ease-out 0 0; + transition:all ease-out 0 0 +} + +.button.general.red .reload { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/reload-black.png) no-repeat; + width:24px; + margin-top:7px; + height:40px; + position:relative; + display:block; + float:left; + margin-right:17px; + top:1px; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.general.red .tick { + background:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tick-black.png) no-repeat; + position:relative; + display:block; + float:right; + width:32px; + height:30px; + margin:9px 25px 0; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.button.general.red .next { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:27px; + width:20px; + line-height:31px; + margin-left:2px +} + +.button.general.red .next:before { + font-family:typeIconFont; + content:"I" +} + +.button.general.red .confirm { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:20px; + width:16px; + line-height:28px; + margin-left:9px +} + +.button.general.red .confirm:before { + font-family:typeIconFont; + content:"H" +} + +.button.general.red .up { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.general.red .up:before { + font-family:typeIconFont; + content:"K" +} + +.button.general.red .down { + display:block; + font-size:27px; + width:21px; + line-height:30px; + margin-left:-4px; + margin-right:2px; + text-align:left; + font-weight:400 +} + +.button.general.red .down:before { + font-family:typeIconFont; + content:"L" +} + +.confirm-skip .confirm .button { + -khtml-opacity:.5; + -moz-opacity:.5; + opacity:.5; + -ms-filter:alpha(Opacity=50) +} + +.confirm-skip .confirm .button:hover { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +.container.confirm { + position:absolute; + bottom:-60px; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + visibility:hidden; + padding:5px +} + +.container.confirm .text { + color:#3f4237; + font-family:Helvetica,Arial; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-size:11px; + margin:16px 8px 0 +} + +.container.confirm .button-wrapper { + position:relative; + z-index:5 +} + +.confirm-nav.confirm-inline #quickyform .questions>li:not(.focus) { + -khtml-opacity:.07; + -moz-opacity:.07; + opacity:.07; + -ms-filter:alpha(Opacity=7); + -moz-transition:bottom 300ms,opacity 300ms; + -webkit-transition:bottom 300ms,opacity 300ms; + -o-transition:bottom 300ms,opacity 300ms; + transition:bottom 300ms,opacity 300ms +} + +.confirm-nav.confirm-inline #quickyform .questions>li:not(.focus) .item { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + +.confirm-nav.confirm-inline .focus .container.confirm { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + visibility:visible; + bottom:-65px; + -moz-transition:bottom 150ms,opacity 150ms; + -webkit-transition:bottom 150ms,opacity 150ms; + -o-transition:bottom 150ms,opacity 150ms; + transition:bottom 150ms,opacity 150ms +} + +.confirm-nav.confirm-inline .focus.error .control .container.confirm { + bottom:-105px +} + +input,textarea { + display:block; + color:#3f4237; + resize:none; + background:0 0; + outline:0; + font-family:Arvo,Georgia,serif; + -webkit-font-smoothing:antialiased; + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + font-size:30px; + line-height:40px; + padding:3px; + border:0; + margin:2px +} + +@media (max-width:800px) { +input,textarea { + font-size:28px; + line-height:35px +} +} + +@media (max-width:550px),(max-height:450px) { +input,textarea { + font-size:24px; + line-height:30px +} +} + +input.blur,textarea.blur { + border:2px dashed #3f4237; + border:2px dashed rgba(63,66,55,.4); + margin:0 +} + +textarea { + border-left:2px dashed #3f4237!important; + border-left:2px dashed rgba(63,66,55,.4)!important; + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0 +} + +.ready input,.ready textarea { + border-radius:5px; + -webkit-border-radius:5px; + -moz-border-radius:5px; + border:2px dashed #3f4237; + border:2px dashed rgba(63,66,55,.4); + -moz-transition:border 0 linear 600ms,border-radius 0 linear 600ms; + -webkit-transition:border 0 linear 600ms,border-radius 0 linear 600ms; + -o-transition:border 0 linear 600ms,border-radius 0 linear 600ms; + transition:border 0 linear 600ms,border-radius 0 linear 600ms +} + +.ready input:focus,.ready textarea:focus { + border-radius:0; + -webkit-border-radius:0; + -moz-border-radius:0; + border:0 +} + +.attachment .content-wrapper .attachment-wrapper .attachment { + display:block; + margin-bottom:20px +} + +.attachment .content-wrapper .attachment-wrapper .attachment img { + display:none +} + +.message { + position:relative; + display:inline-block; + display:none; + z-index:3; + padding:5px 8px; + border-radius:2px; + -webkit-border-radius:2px; + -moz-border-radius:2px; + background-color:#900; + margin-top:20px +} + +.message span { + font-family:Helvetica,Arial; + color:#fff; + font-size:13px +} + +.message div { + width:0; + height:0; + border-left:5px solid transparent; + border-right:5px solid transparent; + border-bottom:5px solid #900; + position:absolute; + top:-5px; + left:10px +} + +.message.left div { + width:0; + height:0; + border-top:5px solid transparent; + border-bottom:5px solid transparent; + border-right:5px solid #900; + position:absolute; + top:7px; + left:-10px +} + +.message.right div { + width:0; + height:0; + border-top:5px solid transparent; + border-bottom:5px solid transparent; + border-left:5px solid #900; + position:absolute; + top:7px; + right:-10px; + left:auto +} + +.letter { + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + width:16px; + padding:0; + height:17px; + font-size:12px; + line-height:20px; + border:1px solid #000; + border:1px solid rgba(0,0,0,.3); + margin-right:7px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + text-align:center; + -webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,.3); + -moz-box-shadow:inset 0 1px 1px rgba(255,255,255,.3); + box-shadow:inset 0 1px 1px rgba(255,255,255,.3); + font-weight:700 +} + +.letter.dark-shadow { + text-shadow:0 1px 1px #fff +} + +.letter.light-shadow { + text-shadow:0 -1px 1px #000 +} + +.letter img { + position:absolute; + right:0; + top:-10px; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -webkit-animation:hand-effect 1000ms 0; + -moz-animation:hand-effect 1000ms 0; + -ms-animation:hand-effect 1000ms 0; + z-index:10 +} + +@-webkit-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-moz-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-ms-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +.letter span { + font-family:Helvetica,Arial; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.letter .tooltip-key { + position:absolute; + right:-3px; + top:-3px; + bottom:-3px; + padding:0 4px; + border:1px solid rgba(0,0,0,.4); + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + line-height:20px; + z-index:5 +} + +.letter .tooltip-key span { + letter-spacing:0; + display:block; + font-weight:400 +} + +.letter .tooltip-key span.t { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40); + padding-right:11px +} + +.letter .tooltip-key span.k { + position:absolute; + top:0; + right:4px; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +.tab-key { + font-size:10px; + width:34px; + height:14px; + padding:2px; + border-width:1px; + border-style:solid; + line-height:12px; + border-radius:3px; + -webkit-border-radius:3px; + -moz-border-radius:3px; + margin-top:10px; + font-weight:700 +} + +.screen { + position:absolute; + left:0; + top:0; + bottom:0; + right:0; + width:100%; + z-index:11; + text-align:center +} + +.screen .button-text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237; + top:11px; + position:absolute; + left:100%; + width:150px; + text-align:left +} + +.screen .attachment { + margin:0 auto +} + +.screen .attachment .image,.screen .attachment .video { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline +} + +.screen .attachment .image iframe,.screen .attachment .video iframe { + height:99.5%!important +} + +.screen .attachment img { + display:none +} + +.screen.layout-float .content-wrapper .content .media { + display:inline; + float:left +} + +.screen.no-footer .footer { + visibility:hidden +} + +.screen.outro.default .content-wrapper .content .text { + font-size:20px; + line-height:20px; + height:30px +} + +.screen.outro.default .content-wrapper .content .credits { + margin-top:10px; + line-height:16px +} + +.screen .content-wrapper { + position:relative; + height:100%; + width:100% +} + +.screen .content-wrapper .content { + padding-left:55px; + padding-right:55px; + max-width:800px; + line-height:40%; + overflow:hidden; + margin:0 auto +} + +.screen .content-wrapper .content.scrollable { + padding-bottom:90px +} + +.screen .content-wrapper .content.scrollable .button-text { + display:none +} + +.screen .content-wrapper .content .media { + display:block +} + +.screen .content-wrapper .content .text { + color:#6d8524; + font-family:Arvo,Georgia,serif; + font-size:24px; + line-height:36px +} + +@media (max-width:800px) { +.screen .content-wrapper .content .text { + font-size:22px; + line-height:33px +} +} + +@media (max-width:550px),(max-height:450px) { +.screen .content-wrapper .content .text { + font-size:20px; + line-height:30px +} +} + +.screen .content-wrapper .content .description { + margin:20px 0 10px; + color:#6d8524; + font-family:Arvo,Georgia,serif; + font-size:19px; + line-height:145%; + opacity:.8; + -webkit-font-smoothing:antialiased +} + +@media (max-width:800px) { +.screen .content-wrapper .content .description { + font-size:17px; + margin-top:29px +} +} + +@media (max-width:550px),(max-height:450px) { +.screen .content-wrapper .content .description { + font-size:16px; + margin-top:28px +} +} + +.screen .content-wrapper .content .button-wrapper { + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + clear:both +} + +.screen .content-wrapper .content .button-wrapper .button { + margin:4px; + text-decoration:none +} + +.screen .content-wrapper .content .button-wrapper .button a { + font-family:Arvo,Georgia,serif; + color:inherit +} + +.screen .content-wrapper .content .button-wrapper .label { + text-align:left; + padding-left:2px; + padding-top:2px; + margin-bottom:4px +} + +.screen .content-wrapper .content .button-wrapper .tooltip.show { + position:absolute; + margin-top:5px; + top:0; + display:block; + width:165px; + left:100% +} + +.screen .content-wrapper .content .placeholder { + position:relative; + border-radius:4px; + -webkit-border-radius:4px; + -moz-border-radius:4px; + background:#ccc; + opacity:.5; + margin-bottom:40px +} + +.screen .content-wrapper .content .placeholder .wrapper { + position:absolute; + top:50%; + margin-top:-86px; + width:100%; + height:100%; + text-align:center; + font-family:Helvetica,Arial +} + +.screen .content-wrapper .content .placeholder .wrapper h2 { + color:#333; + font-size:20px; + font-weight:400; + line-height:150%; + margin-top:10px +} + +.screen .content-wrapper .content .placeholder .wrapper h3 { + color:#666; + font-size:12px; + font-weight:400; + line-height:140% +} + +.screen .content-wrapper .content .social { + display:none +} + +.screen .content-wrapper .content .social a { + border:0 +} + +.screen .content-wrapper .content .social a img { + border:0 +} + +.screen .content-wrapper .content .social.show { + display:block +} + +.screen .content-wrapper .content .social,.screen .content-wrapper .content .credits { + margin-top:30px +} + +.screen .footer { + position:fixed; + bottom:0; + display:none; + z-index:2; + left:0; + right:0; + overflow:hidden; + height:70px; + border-top-style:solid; + border-top-width:1px +} + +.screen .footer.step0 { + border-color:rgba(0,0,0,.2); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.15); + -moz-box-shadow:0 0 3px rgba(0,0,0,.15); + box-shadow:0 0 3px rgba(0,0,0,.15) +} + +.screen .footer.step0 .overlay { + background-color:#000; + -khtml-opacity:.02; + -moz-opacity:.02; + opacity:.02; + -ms-filter:alpha(Opacity=2) +} + +.screen .footer.step1 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.2); + -moz-box-shadow:0 0 3px rgba(0,0,0,.2); + box-shadow:0 0 3px rgba(0,0,0,.2) +} + +.screen .footer.step1 .overlay { + background-color:#000; + -khtml-opacity:.04; + -moz-opacity:.04; + opacity:.04; + -ms-filter:alpha(Opacity=4) +} + +.screen .footer.step2 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.25); + -moz-box-shadow:0 0 3px rgba(0,0,0,.25); + box-shadow:0 0 3px rgba(0,0,0,.25) +} + +.screen .footer.step2 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.screen .footer.step3 { + border-color:rgba(0,0,0,.25); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.3); + -moz-box-shadow:0 0 3px rgba(0,0,0,.3); + box-shadow:0 0 3px rgba(0,0,0,.3) +} + +.screen .footer.step3 .overlay { + background-color:#000; + -khtml-opacity:.05; + -moz-opacity:.05; + opacity:.05; + -ms-filter:alpha(Opacity=5) +} + +.screen .footer.step4 { + border-color:rgba(0,0,0,.4); + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45) +} + +.screen .footer.step4 .overlay { + background-color:#000; + -khtml-opacity:.08; + -moz-opacity:.08; + opacity:.08; + -ms-filter:alpha(Opacity=8) +} + +.screen .footer.step5 { + -webkit-box-shadow:0 0 3px rgba(0,0,0,.45); + -moz-box-shadow:0 0 3px rgba(0,0,0,.45); + box-shadow:0 0 3px rgba(0,0,0,.45); + border-top:1px solid rgba(255,255,255,.15) +} + +.screen .footer.step5 .overlay { + background-color:#fff; + -khtml-opacity:.06; + -moz-opacity:.06; + opacity:.06; + -ms-filter:alpha(Opacity=6) +} + +.screen .footer .content { + position:relative; + z-index:6; + margin:0 auto +} + +.screen .footer .button-text { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + font-family:Helvetica,Arial; + font-size:11px; + margin:16px 8px 0; + color:#3f4237; + top:4px; + position:absolute; + left:100%; + width:150px; + text-align:left +} + +.screen .footer .overlay { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + z-index:2 +} + +.screen .footer .persistent-wrapper,.screen .footer .background,.screen .footer .overlayColor { + position:absolute; + top:0; + bottom:0; + left:0; + right:0 +} + +.screen .footer .background { + background-color:#c1ef41; + top:0; + z-index:2 +} + +.screen .footer .overlayColor { + z-index:3 +} + +.screen .footer .button-wrapper { + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + margin-top:8px +} + +.screen .footer .button-wrapper .button { + font-family:Arvo,Georgia,serif +} + +.screen .footer .button-wrapper .label { + text-align:left; + line-height:54px +} + +.screen .footer .button-wrapper .tooltip { + margin-top:5px +} + +.screen .footer .button-wrapper .tooltip.show { + position:absolute; + margin-top:5px; + top:0; + display:block; + width:165px; + left:100% +} + +.statement div.question span { + line-height:145%; + min-height:28px +} + +.statement div.content { + margin-top:0 +} + +.statement div.item { + width:40px; + font-size:56px; + top:20px; + line-height:normal +} + +.statement.sub-question.connected .item { + top:-10px +} + +.textfield div.input,.email div.input,.website div.input,.number div.input { + position:relative; + margin-left:-5px; + height:55px +} + +.textfield div.input input,.email div.input input,.website div.input input,.number div.input input { + position:absolute; + width:100% +} + + .questions .textarea.focus .wrapper { + height:auto!important +} + + .questions .textarea .textarea-wrapper { + margin-left:-5px; + padding-right:0; + position:relative +} + + .questions .textarea .textarea-wrapper textarea { + position:relative; + height:36px; + overflow:hidden; + width:100%; + margin-bottom:3px +} + + .questions .textarea .keyboard-tip { + display:none; + position:relative; + font-size:11px; + padding:20px 0 +} + + .questions .textarea .keyboard-tip .aux-border { + position:absolute; + height:1px; + left:0; + top:0; + right:0; + border-top:1px; + border-style:solid; + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20) +} + + .questions .textarea .keyboard-tip.show { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline +} + + .questions .textarea .keyboard-tip .tab-key { + margin-top:-2px +} + + .questions .textarea .keyboard-tip .key { + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + margin-left:7px +} + + .questions .textarea .keyboard-tip span { + -khtml-opacity:.6; + -moz-opacity:.6; + opacity:.6; + -ms-filter:alpha(Opacity=60) +} + + .questions .textarea .keyboard-tip span b { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + + .questions .textarea .keyboard-tip .hand1 { + position:absolute; + top:-12px; + left:-5px; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -webkit-animation:hand-effect 1000ms 0; + -moz-animation:hand-effect 1000ms 0; + -ms-animation:hand-effect 1000ms 0 +} + +@-webkit-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-moz-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-ms-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + + .questions .textarea .keyboard-tip .hand2 { + position:absolute; + top:-10px; + right:0; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -webkit-animation:hand-effect 1000ms 0; + -moz-animation:hand-effect 1000ms 0; + -ms-animation:hand-effect 1000ms 0 +} + +@-webkit-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-moz-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + +@-ms-keyframes hand-effect { +10%,87% { + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100) +} + +0%,28%,56%,84% { + margin-top:-3px +} + +14%,42%,70%,100% { + margin-top:0 +} +} + + .questions .list .multiple { + margin:10px 0; + font-size:16px; + display:none; + color:#6d8524 +} + + .questions .list.multiple .multiple { + display:block; + -khtml-opacity:.7; + -moz-opacity:.7; + opacity:.7; + -ms-filter:alpha(Opacity=70) +} + + .questions .list.multiple .control { + padding-left:10px; + border-left-width:1px; + border-left-style:dashed; + border-color:#3f4237 +} + + .questions .list .button-wrapper.confirm .button.visible { + visibility:visible; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + -moz-transition:opacity ease-out 200ms 0; + -webkit-transition:opacity ease-out 200ms 0; + -o-transition:opacity ease-out 200ms 0; + transition:opacity ease-out 200ms 0 +} + + .questions .list.vertical ul li { + margin:4px 3px 8px; + display:block +} + +.resize-precalculate .questions .list.vertical ul li { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline +} + + .questions .list ul { + margin:0; + margin-left:-3px; + display:block; + outline:0; + text-align:left; + margin-bottom:25px +} + + .questions .list ul li { + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + cursor:pointer; + margin:4px 3px; + list-style:none; + color:#fff; + padding:8px 37px +} + + .questions .list ul li .tick { + color:#3f4237; + top:50%; + margin-top:-12px +} + + .questions .list ul li .label { + color:#3f4237; + font-size:21px +} + +@media (max-width:800px) { + .questions .list ul li .label { + font-size:20px +} +} + +@media (max-width:550px),(max-height:450px) { + .questions .list ul li .label { + font-size:19px +} +} + + .questions .list ul li .letter { + color:#3f4237; + position:absolute; + left:10px; + top:10px +} + +@media (max-width:800px) { + .questions .list ul li .letter { + left:9px; + top:9px +} +} + +@media (max-width:550px),(max-height:450px) { + .questions .list ul li .letter { + left:8px; + top:8px +} +} + + .questions .list ul li.custom { + float:none; + clear:both; + position:relative; + -moz-transition:width ease-out 100ms 0; + -webkit-transition:width ease-out 100ms 0; + -o-transition:width ease-out 100ms 0; + transition:width ease-out 100ms 0 +} + + .questions .list ul li.custom input { + display:none; + position:relative; + width:100%; + padding:0; + background:0 0; + border:0; + font-size:21px; + z-index:10; + height:31px; + line-height:0 +} + + .questions .list ul li.custom .label { + display:block +} + + .questions .list ul li.custom .ok-confirm { + position:absolute; + top:2px; + right:2px; + display:none; + z-index:7 +} + + .questions .list ul li.custom .ok-confirm .button { + position:relative; + margin:0; + width:13px; + height:27px; + right:0; + padding:2px 12px 4px +} + + .questions .list ul li.custom.open { + -moz-transition:width ease-out 100ms 0; + -webkit-transition:width ease-out 100ms 0; + -o-transition:width ease-out 100ms 0; + transition:width ease-out 100ms 0; + padding:2px 50px 2px 37px +} + + .questions .list ul li.custom.open .ok-confirm { + display:block +} + + .questions .list ul li.custom.open .label { + display:none +} + + .questions .list ul li.custom.open input { + display:block +} + +.list-image div.content .content-wrapper>.button-wrapper { + margin-top:20px +} + +.list-image div.message { + margin-top:20px; + margin-bottom:0 +} + +.list-image .multiple { + margin-bottom:10px; + font-size:16px; + display:none; + color:#6d8524 +} + +.list-image.multiple .multiple { + display:block +} + +.list-image.multiple .content-wrapper { + padding-left:10px; + border-left-width:1px; + border-left-style:dashed +} + +.list-image ul { + padding:0 +} + +.list-image li { + cursor:pointer; + word-wrap:break-word; + padding:5px; + float:left; + padding-bottom:53px; + list-style:none; + margin:2px 12px 12px 2px +} + +.list-image li .image-wrapper { + position:relative; + overflow:hidden +} + +.list-image li .image-wrapper img { + display:none; + position:absolute; + left:50%; + top:50% +} + +.list-image li .tick-wrapper { + display:none +} + +.list-image li.selected .tick-wrapper { + z-index:3; + display:block; + position:absolute; + top:-1px; + right:1px; + width:48px; + height:50px; + background-image:url(https://s3-eu-west-1.amazonaws.com/typeform-media-static/tickbg.png) +} + +.list-image li.selected .tick { + display:block; + z-index:4; + color:#000; + position:absolute; + top:2px; + right:2px; + -khtml-opacity:.7; + -moz-opacity:.7; + opacity:.7; + -ms-filter:alpha(Opacity=70); + margin:0 +} + +.list-image li.selected .tick:before { + font-family:typeIconFont; + content:"H" +} + +.list-image li .text { + position:absolute; + text-align:center; + left:0; + right:0; + bottom:6px; + height:46px +} + +.list-image li .text .label { + color:#3f4237; + position:relative; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline; + text-align:left; + height:31px; + line-height:15px; + font-size:13px; + padding-top:17px; + padding-left:30px; + vertical-align:middle; + padding-right:5px; + -webkit-font-smoothing:subpixel-antialiased +} + +.list-image li .text .label .caption { + word-break:break-word; + overflow:hidden; + height:100% +} + +.list-image li .text .letter { + color:#3f4237; + position:absolute; + margin-top:-2px; + left:6px +} + +.list-image li span.val { + position:absolute; + top:50%; + margin-top:-16px; + text-align:center; + width:100%; + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.list-image li.custom input { + position:absolute; + left:10px; + right:10px; + top:50%; + display:none; + margin:0; + margin-top:-25px; + padding:0; + background:0 0; + border:0; + font-size:16px; + z-index:10 +} + +.list-image li.custom .button-wrapper { + position:absolute; + top:50%; + left:50%; + margin-left:-16px; + margin-top:20px; + display:none; + z-index:10 +} + +.list-image li.custom .button-wrapper .tick { + margin-top:0; + -khtml-opacity:1; + -moz-opacity:1; + opacity:1; + -ms-filter:alpha(Opacity=100); + position:relative; + display:block; + width:17px +} + +.list-image li.custom.open span.val { + display:none +} + +.list-image li.custom.open img { + -khtml-opacity:.4; + -moz-opacity:.4; + opacity:.4; + -ms-filter:alpha(Opacity=40) +} + +.list-image li.custom.open input,.list-image li.custom.open .ok-confirm { + display:block +} + +.yes-no .scroll-area,.terms .scroll-area { + color:#3f4237; + -webkit-touch-callout:text; + -webkit-user-select:text; + -khtml-user-select:text; + -moz-user-select:text; + -ms-user-select:text; + user-select:text; + line-height:140%; + margin-bottom:30px; + font-size:13px; + -webkit-font-smoothing:antialiased +} + +.yes-no ul,.terms ul { + margin-bottom:10px; + color:#3f4237 +} + +.yes-no ul li,.terms ul li { + margin:9px 0; + padding:5px 35px 5px 5px; + font-size:21px; + line-height:27px; + cursor:pointer +} + +.resize-precalculate .yes-no ul li,.resize-precalculate .terms ul li { + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display:inline +} + +@media (max-width:800px) { +.yes-no ul li,.terms ul li { + font-size:20px +} +} + +@media (max-width:550px),(max-height:450px) { +.yes-no ul li,.terms ul li { + font-size:19px +} +} + +.yes-no ul li .letter,.terms ul li .letter { + margin:4px +} + +.terms .description { + display:none +} + +.rating .icons { + color:#3f4237 +} + +.rating .icons li { + float:left; + cursor:pointer; + margin-right:7px +} + +.rating .icons li .icon { + padding-top:1px; + font-weight:400; + font-style:normal; + display:inline-block; + text-decoration:inherit +} + +.rating .icons li .icon.back { + position:absolute; + top:0; + left:0; + right:0; + -khtml-opacity:0; + -moz-opacity:0; + opacity:0; + -ms-filter:alpha(Opacity=0); + -moz-transition:opacity ease-out 100ms 0; + -webkit-transition:opacity ease-out 100ms 0; + -o-transition:opacity ease-out 100ms 0; + transition:opacity ease-out 100ms 0 +} + +.rating .icons li.hover .back,.rating .icons li.pre-selected .back { + -khtml-opacity:.2; + -moz-opacity:.2; + opacity:.2; + -ms-filter:alpha(Opacity=20); + -moz-transition:opacity ease-out 100ms 0; + -webkit-transition:opacity ease-out 100ms 0; + -o-transition:opacity ease-out 100ms 0; + transition:opacity ease-out 100ms 0 +} + +.rating .icons li.selected .icon { + padding-top:0; + padding-bottom:1px +} + +.rating .icons li .center-wrapper { + display:table +} + +.rating .icons li .center { + position:relative +} + +.rating .icons li .label { + font-size:12px; + display:block; + text-align:center; + margin-top:10px +} + +.rating .icons li .letter { + margin:0 +} + +.rating.icon-star .icons .icon:before { + font-family:typeIconFont; + content:"a" +} + +.rating.icon-star .icons .selected .icon,.rating.icon-star .icons .back { + text-transform:uppercase +} + +.rating.icon-star .icons .selected .icon:before,.rating.icon-star .icons .back:before { + font-family:typeIconFont; + content:"A" +} + +.rating.icon-heart .icons .icon:before { + font-family:typeIconFont; + content:"b" +} + +.rating.icon-heart .icons .selected .icon,.rating.icon-heart .icons .back { + text-transform:uppercase +} + +.rating.icon-heart .icons .selected .icon:before,.rating.icon-heart .icons .back:before { + font-family:typeIconFont; + content:"B" +} + +.rating.icon-user .icons .icon:before { + font-family:typeIconFont; + content:"c" +} + +.rating.icon-user .icons .selected .icon,.rating.icon-user .icons .back { + text-transform:uppercase +} + +.rating.icon-user .icons .selected .icon:before,.rating.icon-user .icons .back:before { + font-family:typeIconFont; + content:"C" +} + +.rating.icon-up .icons .icon:before { + font-family:typeIconFont; + content:"d" +} + +.rating.icon-up .icons .selected .icon,.rating.icon-up .icons .back { + text-transform:uppercase +} + +.rating.icon-up .icons .selected .icon:before,.rating.icon-up .icons .back:before { + font-family:typeIconFont; + content:"D" +} + +.rating.icon-crown .icons .icon:before { + font-family:typeIconFont; + content:"e" +} + +.rating.icon-crown .icons .selected .icon,.rating.icon-crown .icons .back { + text-transform:uppercase +} + +.rating.icon-crown .icons .selected .icon:before,.rating.icon-crown .icons .back:before { + font-family:typeIconFont; + content:"E" +} + +.rating.icon-cat .icons .icon:before { + font-family:typeIconFont; + content:"f" +} + +.rating.icon-cat .icons .selected .icon,.rating.icon-cat .icons .back { + text-transform:uppercase +} + +.rating.icon-cat .icons .selected .icon:before,.rating.icon-cat .icons .back:before { + font-family:typeIconFont; + content:"F" +} + +.rating.icon-dog .icons .icon:before { + font-family:typeIconFont; + content:"n" +} + +.rating.icon-dog .icons .selected .icon,.rating.icon-dog .icons .back { + text-transform:uppercase +} + +.rating.icon-dog .icons .selected .icon:before,.rating.icon-dog .icons .back:before { + font-family:typeIconFont; + content:"N" +} + +.rating.icon-circle .icons .icon:before { + font-family:typeIconFont; + content:"o" +} + +.rating.icon-circle .icons .selected .icon,.rating.icon-circle .icons .back { + text-transform:uppercase +} + +.rating.icon-circle .icons .selected .icon:before,.rating.icon-circle .icons .back:before { + font-family:typeIconFont; + content:"O" +} + +.rating.icon-flag .icons .icon:before { + font-family:typeIconFont; + content:"p" +} + +.rating.icon-flag .icons .selected .icon,.rating.icon-flag .icons .back { + text-transform:uppercase +} + +.rating.icon-flag .icons .selected .icon:before,.rating.icon-flag .icons .back:before { + font-family:typeIconFont; + content:"P" +} + +.rating.icon-droplet .icons .icon:before { + font-family:typeIconFont; + content:"q" +} + +.rating.icon-droplet .icons .selected .icon,.rating.icon-droplet .icons .back { + text-transform:uppercase +} + +.rating.icon-droplet .icons .selected .icon:before,.rating.icon-droplet .icons .back:before { + font-family:typeIconFont; + content:"Q" +} + +.rating.icon-tick .icons .icon:before { + font-family:typeIconFont; + content:"r" +} + +.rating.icon-tick .icons .selected .icon,.rating.icon-tick .icons .back { + text-transform:uppercase +} + +.rating.icon-tick .icons .selected .icon:before,.rating.icon-tick .icons .back:before { + font-family:typeIconFont; + content:"R" +} + +.rating.icon-lightbulb .icons .icon:before { + font-family:typeIconFont; + content:"s" +} + +.rating.icon-lightbulb .icons .selected .icon,.rating.icon-lightbulb .icons .back { + text-transform:uppercase +} + +.rating.icon-lightbulb .icons .selected .icon:before,.rating.icon-lightbulb .icons .back:before { + font-family:typeIconFont; + content:"S" +} + +.rating.icon-trophy .icons .icon:before { + font-family:typeIconFont; + content:"t" +} + +.rating.icon-trophy .icons .selected .icon,.rating.icon-trophy .icons .back { + text-transform:uppercase +} + +.rating.icon-trophy .icons .selected .icon:before,.rating.icon-trophy .icons .back:before { + font-family:typeIconFont; + content:"T" +} + +.rating.icon-cloud .icons .icon:before { + font-family:typeIconFont; + content:"u" +} + +.rating.icon-cloud .icons .selected .icon,.rating.icon-cloud .icons .back { + text-transform:uppercase +} + +.rating.icon-cloud .icons .selected .icon:before,.rating.icon-cloud .icons .back:before { + font-family:typeIconFont; + content:"U" +} + +.rating.icon-thunderbolt .icons .icon:before { + font-family:typeIconFont; + content:"v" +} + +.rating.icon-thunderbolt .icons .selected .icon,.rating.icon-thunderbolt .icons .back { + text-transform:uppercase +} + +.rating.icon-thunderbolt .icons .selected .icon:before,.rating.icon-thunderbolt .icons .back:before { + font-family:typeIconFont; + content:"V" +} + +.rating.icon-pencil .icons .icon:before { + font-family:typeIconFont; + content:"w" +} + +.rating.icon-pencil .icons .selected .icon,.rating.icon-pencil .icons .back { + text-transform:uppercase +} + +.rating.icon-pencil .icons .selected .icon:before,.rating.icon-pencil .icons .back:before { + font-family:typeIconFont; + content:"W" +} + +.rating.icon-skull .icons .icon:before { + font-family:typeIconFont; + content:"x" +} + +.rating.icon-skull .icons .selected .icon,.rating.icon-skull .icons .back { + text-transform:uppercase +} + +.rating.icon-skull .icons .selected .icon:before,.rating.icon-skull .icons .back:before { + font-family:typeIconFont; + content:"X" +} + +.group #placeholder { + margin-left:30px +} + +.group #placeholder span { + display:block; + -webkit-font-smoothing:antialiased; + font-size:20px; + line-height:30px; + color:rgba(109,133,36,.4) +} + +.group #placeholder span:first-child { + color:rgba(109,133,36,.6) +} + +.group #placeholder span:nth-child(2) { + color:rgba(109,133,36,.5) +} + + .form .questions>li.group.connected .wrapper { + padding-bottom:25px; + padding-top:30px +} + + .form .questions>li.connected .wrapper { + padding-top:0 +} + + .form .questions>li.connected .message { + margin-top:0 +} + +.dropdown .message { + margin-top:10px +} + +.dropdown .not-found { + position:relative; + visibility:hidden; + top:0; + padding:2px; + display:block +} + +.dropdown.not-found .not-found { + font-size:14px; + visibility:visible; + top:5px; + -moz-transition:top ease-out 100ms 0; + -webkit-transition:top ease-out 100ms 0; + -o-transition:top ease-out 100ms 0; + transition:top ease-out 100ms 0 +} + +.dropdown.not-found .input-wrapper .triangle { + display:none +} + +.dropdown.not-found .input-wrapper .cross { + display:block +} + +.dropdown .input-wrapper { + position:relative +} + +.dropdown .input-wrapper input { + border:0; + border-bottom:1px solid rgba(63,66,55,.4); + display:block; + width:100% +} + +.dropdown .input-wrapper .triangle { + padding:10px 0; + position:absolute; + z-index:10; + right:0; + top:8px; + border:0 +} + +.dropdown .input-wrapper .triangle span { + position:relative; + top:17px; + width:0; + height:0; + border-left:7px solid transparent; + border-right:7px solid transparent; + border-top:8px solid rgba(63,66,55,.4) +} + +@media (max-width:800px) { +.dropdown .input-wrapper .triangle span { + top:14px +} +} + +@media (max-width:550px),(max-height:450px) { +.dropdown .input-wrapper .triangle span { + top:10px +} +} + +.dropdown .input-wrapper .triangle:hover span { + border-top:8px solid rgba(63,66,55,.6) +} + +.dropdown .input-wrapper .cross { + display:none; + padding:10px; + position:absolute; + z-index:10; + right:-10px; + top:4px; + text-decoration:none; + -webkit-font-smoothing:antialiased; + font-size:19px; + border:0 +} + +.dropdown .input-wrapper .cross:hover span { + color:#5a5e4e +} + +.dropdown.focus .dropdown-menu { + visibility:visible +} + +.dropdown .dropdown-menu { + position:relative; + z-index:10; + overflow:auto; + margin-top:7px; + visibility:hidden +} + +.dropdown .dropdown-menu li .tick { + position:absolute; + right:5px; + top:24px +} + +.dropdown .dropdown-menu li a { + padding:10px; + margin:5px; + display:block; + font-size:20px; + text-decoration:none; + cursor:pointer; + z-index:10; + position:relative; + -webkit-font-smoothing:antialiased; + border:0 +} + +.dropdown .dropdown-menu li .aux .overlay { + display:none +} + +.dropdown .dropdown-menu li.active .aux .overlay { + display:block +} + +.keyboard-icon { + font-family:keyboard; + color:rgba(63,66,55,.4); + position:absolute +} + +.keyboard-icon.hidden { + display:none +} + +.nano { + position:absolute; + width:100%; + height:100%; + overflow:hidden +} + +.nano .nano-content { + overflow:hidden; + position:absolute; + top:0; + right:13px!important; + bottom:0; + left:0 +} + +.nano .nano-content:focus { + outline:thin dotted +} + +.nano .nano-content::-webkit-scrollbar { + visibility:hidden +} + +.nano>.pane { + background-color:rgba(63,66,55,.25); + position:absolute; + width:7px; + right:0; + top:7px; + bottom:0; + visibility:hidden\9; + -moz-border-radius:5px; + -webkit-border-radius:5px; + border-radius:5px +} + +.nano>.pane>.slider { + background-color:rgba(63,66,55,.5); + position:relative; + margin:0 1px; + -moz-border-radius:3px; + -webkit-border-radius:3px; + border-radius:3px +} + +.nano>.pane { + width:10px +} + +.nano>.pane>.slider:hover { + cursor:grabbing +} + +.nano:hover>.pane,.pane.active,.pane.flashed { + visibility:visible\9; + opacity:.99 +} + +.has-scrollbar .content::-webkit-scrollbar { + visibility:visible +} + +.padlock-icon { + width:14px; + height:19px; + float:left; + padding:7px 7px 7px 0; + fill:#6d8524 +} + +@media (max-width:800px) { +.padlock-icon { + width:13px; + height:18px; + padding:5px 8px 5px 0 +} +} + +@media (max-width:550px),(max-height:450px) { +.padlock-icon { + width:12px; + height:16px; + padding:5px 8px 5px 0 +} +} + +.container .label,.text,.question span,.header .question,.description,.item span,.button.general,.button.nav { + -webkit-font-smoothing:antialiased +} + +.font-family-bangers .container .label,.font-family-bangers .text,.font-family-bangers .question span,.font-family-bangers .header .question,.font-family-bangers .description,.font-family-bangers .item span,.font-family-bangers .button.general,.font-family-bangers .button.nav { + letter-spacing:.7px +} + +.font-family-lato .container .label,.font-family-lato .text,.font-family-lato .question span,.font-family-lato .header .question,.font-family-lato .description,.font-family-lato .item span,.font-family-lato .button.general,.font-family-lato .button.nav { + -webkit-font-smoothing:subpixel-antialiased!important +} + +.font-family-lekton .container .label,.font-family-lekton .text,.font-family-lekton .question span,.font-family-lekton .header .question,.font-family-lekton .description,.font-family-lekton .item span,.font-family-lekton .button.general,.font-family-lekton .button.nav { + -webkit-font-smoothing:subpixel-antialiased!important +} + +.font-family-quicksand .container .label,.font-family-quicksand .text,.font-family-quicksand .question span,.font-family-quicksand .header .question,.font-family-quicksand .description,.font-family-quicksand .item span,.font-family-quicksand .button.general,.font-family-quicksand .button.nav { + -webkit-font-smoothing:subpixel-antialiased!important; + letter-spacing:-.5px +} + +.font-family-raleway .container .label,.font-family-raleway .text,.font-family-raleway .question span,.font-family-raleway .header .question,.font-family-raleway .description,.font-family-raleway .item span,.font-family-raleway .button.general,.font-family-raleway .button.nav { + -webkit-font-smoothing:subpixel-antialiased!important +} + +.font-family-nixie-one .container .label,.font-family-nixie-one .text,.font-family-nixie-one .question span,.font-family-nixie-one .header .question,.font-family-nixie-one .description,.font-family-nixie-one .item span,.font-family-nixie-one .button.general,.font-family-nixie-one .button.nav { + -webkit-font-smoothing:subpixel-antialiased!important +} + +.font-family-bangers .button.general.full,.font-family-bangers .submit>.button.general { + letter-spacing:1.5px; + line-height:42px +} + +.font-family-handlee .button.general.full,.font-family-handlee .submit>.button.general { + line-height:42px +} + +.font-family-karla .button.general.full,.font-family-karla .submit>.button.general { + letter-spacing:-1px +} + +.font-family-lato .button.general.full,.font-family-lato .submit>.button.general { + line-height:36px +} + +.font-family-lekton .button.general.full,.font-family-lekton .submit>.button.general { + line-height:42px +} + +.font-family-quicksand .button.general.full,.font-family-quicksand .submit>.button.general { + letter-spacing:-.5px +} + +.font-family-exo-2 .button.general.full,.font-family-exo-2 .submit>.button.general { + line-height:35px +} + +.font-family-raleway .questions .list ul li { + padding:5px 37px; + line-height:30px; + height:28px +} + +.font-family-raleway .questions .list ul li.custom.open { + padding:2px 50px 9px 37px +} + +.font-family-raleway .questions .list ul li .tick { + margin-top:-15px +} + +.font-family-nixie-one .list-image li .text .label .caption { + -webkit-font-smoothing:antialiased!important; + font-weight:700 +}*/ \ No newline at end of file diff --git a/public/modules/forms/directives/changeFocus.client.directive.js b/public/modules/forms/directives/changeFocus.client.directive.js new file mode 100644 index 00000000..c8116c13 --- /dev/null +++ b/public/modules/forms/directives/changeFocus.client.directive.js @@ -0,0 +1,36 @@ +'use strict'; + +angular.module('forms').directive('changeFocus', function() { + return { + scope:{ + focusDownId: '@', + focusUpId: '@', + }, + link: function(scope, elem, attrs) { + // console.log('aoeuaoeuaoeuaou'); + scope.focusUp = function(){ + if(!scope.$first) { + // console.log('aoeuaoeu'); + elem[0].previousElementSibling.find('input').focus(); + } + scope.apply(); + }; + scope.focusDown = function(){ + if(!scope.$last) { + elem[0].nextElementSibling.focus(); + } + scope.apply(); + }; + + //Bind 'focus-down' click event to given dom element + angular.element('#' + scope.focusDownId).bind('click', function() { + scope.focusDown(); + }); + + //Bind 'focus-up' click event to given dom element + angular.element('#' + scope.focusUpId).bind('click', function() { + scope.focusUp(); + }); + } + }; +}); \ No newline at end of file diff --git a/public/modules/forms/directives/field.client.directive.js b/public/modules/forms/directives/field.client.directive.js new file mode 100644 index 00000000..9af21cee --- /dev/null +++ b/public/modules/forms/directives/field.client.directive.js @@ -0,0 +1,51 @@ +'use strict'; + +// coffeescript's for in loop +var __indexOf = [].indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (i in this && this[i] === item) return i; + } + return -1; +}; + +angular.module('forms').directive('fieldDirective', function($http, $compile) { + + + var getTemplateUrl = function(field) { + + var type = field.fieldType; + var templateUrl = './modules/forms/views/directiveViews/field/'; + var supported_fields = [ + 'textfield', + 'email', + 'textarea', + 'checkbox', + 'date', + 'dropdown', + 'hidden', + 'password', + 'radio' + ]; + if (__indexOf.call(supported_fields, type) >= 0) { + return templateUrl += type + '.html'; + } + }; + + var linker = function(scope, element) { + // GET template content from path + var templateUrl = getTemplateUrl(scope.field); + $http.get(templateUrl).success(function(data) { + element.html(data); + $compile(element.contents())(scope); + }); + }; + + return { + template: '
{{field.title}}
', + restrict: 'E', + scope: { + field: '=' + }, + link: linker + }; +}); \ No newline at end of file diff --git a/public/modules/forms/directives/form.client.directive.js b/public/modules/forms/directives/form.client.directive.js new file mode 100644 index 00000000..b7ef2deb --- /dev/null +++ b/public/modules/forms/directives/form.client.directive.js @@ -0,0 +1,36 @@ +'use strict'; + +angular.module('forms').directive('formDirective', ['$http', '$timeout', 'timeCounter', + function ($http, $timeout, timeCounter) { + return { + controller: function($scope){ + timeCounter.startClock(); + + + $scope.submit = function(){ + var _timeElapsed = timeCounter.stopClock(); + $scope.form.timeElapsed = _timeElapsed; + + console.log($scope.form.timeElapsed); + + $http.post('/forms/'+$scope.form._id,$scope.form). + success(function(data, status, headers){ + console.log('form submitted successfully'); + alert('Form submitted..'); + $scope.form.submitted = true; + }); + }; + + $scope.cancel = function(){ + alert('Form canceled..'); + }; + + }, + templateUrl: './modules/forms/views/directiveViews/form/form.html', + restrict: 'E', + scope: { + form:'=' + } + }; + } +]); \ No newline at end of file diff --git a/public/modules/forms/forms.client.module.js b/public/modules/forms/forms.client.module.js new file mode 100644 index 00000000..8c77200e --- /dev/null +++ b/public/modules/forms/forms.client.module.js @@ -0,0 +1,4 @@ +'use strict'; + +// Use Application configuration module to register a new module +ApplicationConfiguration.registerModule('forms', ['ngFileUpload']); \ No newline at end of file diff --git a/public/modules/forms/services/current-form.client.service.js b/public/modules/forms/services/current-form.client.service.js new file mode 100644 index 00000000..0c1c37ad --- /dev/null +++ b/public/modules/forms/services/current-form.client.service.js @@ -0,0 +1,18 @@ +'use strict'; + +//Forms service used for communicating with the forms REST endpoints +angular.module('forms').service('CurrentForm', ['Forms', + function(Forms){ + + //Private variables + var _form = {}; + + //Public Methods + this.getForm = function() { + return _form; + }; + this.setForm = function(form) { + _form = form; + }; + } +]); \ No newline at end of file diff --git a/public/modules/forms/services/form-fields.client.service.js b/public/modules/forms/services/form-fields.client.service.js new file mode 100644 index 00000000..3310b022 --- /dev/null +++ b/public/modules/forms/services/form-fields.client.service.js @@ -0,0 +1,45 @@ +'use strict'; + +angular.module('forms').service('FormFields', [ + function() { + this.fields = [ + { + name : 'textfield', + value : 'Textfield' + }, + { + name : 'email', + value : 'E-mail' + }, + { + name : 'password', + value : 'Password' + }, + { + name : 'radio', + value : 'Radio Buttons' + }, + { + name : 'dropdown', + value : 'Dropdown List' + }, + { + name : 'date', + value : 'Date' + }, + { + name : 'textarea', + value : 'Text Area' + }, + { + name : 'checkbox', + value : 'Checkbox' + }, + { + name : 'hidden', + value : 'Hidden' + } + ]; + } + +]); \ No newline at end of file diff --git a/public/modules/forms/services/forms.client.service.js b/public/modules/forms/services/forms.client.service.js new file mode 100644 index 00000000..d757be5a --- /dev/null +++ b/public/modules/forms/services/forms.client.service.js @@ -0,0 +1,28 @@ +'use strict'; + +//Forms service used for communicating with the forms REST endpoints +angular.module('forms').factory('Forms', ['$resource', + function($resource) { + return $resource('forms/:formId', { + formId: '@_id' + }, { + 'query' : { + method: 'GET', + isArray: false, + // "transformResponse": function (data) { + // var _data = JSON.parse(data); + // var _pdf = JSON.parse(data).pdf; + + // _data.pdf = _pdf; + // return _data; + // } + }, + 'update': { + method: 'PUT' + }, + 'save': { + method: 'POST' + } + }); + } +]); \ No newline at end of file diff --git a/public/modules/forms/services/time-counter.client.service.js b/public/modules/forms/services/time-counter.client.service.js new file mode 100644 index 00000000..cbd19de0 --- /dev/null +++ b/public/modules/forms/services/time-counter.client.service.js @@ -0,0 +1,22 @@ +'use strict'; + +angular.module('forms').service('timeCounter', [ + function(){ + var _startTime, _endTime, that=this; + + this.timeSpent; + + this.startClock = function(){ + _startTime = Date.now(); + console.log('Clock Started'); + }; + + this.stopClock = function(){ + _endTime = Date.now(); + that.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000; + console.log('Clock Ended'); + return that.timeSpent; + }; + + } +]); \ No newline at end of file diff --git a/public/modules/forms/views/create-form.client.view.html b/public/modules/forms/views/create-form.client.view.html new file mode 100644 index 00000000..7eb80bf9 --- /dev/null +++ b/public/modules/forms/views/create-form.client.view.html @@ -0,0 +1,227 @@ +
+
+

Create your form


+
+

Select field type you want to add to the form below and click on 'Add Field' button. Don't forget to set field properties. After you finish creating the form, you can preview the form by clicking Preview Form button.

+
+
+
+

Edit your form


+
+ + +
+
+
+

Form Title

+
+
+

+
+ +
+
+

Fields

+
+
+ +
+
+ + +
+ +
+

No fields added yet.

+ + + +
+ + +
+
Field ID:
+
{{field.client_id}}
+
+
+
Field Type:
+
{{field.fieldType}}
+
+ +

+ +
+
Field Title:
+
+
+
+
Field Default Value:
+
+
+
+
Field Options:
+
+
+ + + Value: {{ option.option_value }} +
+ +
+
+ +

+ +
+
Required:
+
+ + +
+
+ +

+ +
+
Disabled:
+
+ + +
+
+
+
+
+
+
+ +
+
+

Form PDF

+
+
+ +
+
+
Upload your PDF
+
+
+
+
+ +
+ {{form.pdf.originalname}} +
+
+
+ + + + +
+ + Upload your PDF +
+
+
+
+
+
+
+
Autogenerate Form?
+
+

+
+ + + + + + +
+
+ +
+
+
Save Submissions as PDFs?
+
+

+
+ + + + + + +
+
+ +

+
+
+ +
+
+ + +
+
+ +

+ + + +
+ + + +
+ + +

+ +

+
+
+ +
\ No newline at end of file diff --git a/public/modules/forms/views/directiveViews/field/checkbox.html b/public/modules/forms/views/directiveViews/field/checkbox.html new file mode 100755 index 00000000..76370bc0 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/checkbox.html @@ -0,0 +1,7 @@ +
+
+ + + (* required) +
+ diff --git a/public/modules/forms/views/directiveViews/field/date.html b/public/modules/forms/views/directiveViews/field/date.html new file mode 100755 index 00000000..3054e365 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/date.html @@ -0,0 +1,10 @@ +
+
{{field.title}}:
+ * required +
+
+ + +
+
+
diff --git a/public/modules/forms/views/directiveViews/field/dropdown.html b/public/modules/forms/views/directiveViews/field/dropdown.html new file mode 100755 index 00000000..e363e28b --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/dropdown.html @@ -0,0 +1,14 @@ +
+
{{field.title}}:
+
+ + * required +
+
+
diff --git a/public/modules/forms/views/directiveViews/field/email.html b/public/modules/forms/views/directiveViews/field/email.html new file mode 100755 index 00000000..bfde4397 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/email.html @@ -0,0 +1,7 @@ +
+
{{field.title}}:
+
+ + * required +
+
diff --git a/public/modules/forms/views/directiveViews/field/hidden.html b/public/modules/forms/views/directiveViews/field/hidden.html new file mode 100755 index 00000000..3303d1b9 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/hidden.html @@ -0,0 +1 @@ + diff --git a/public/modules/forms/views/directiveViews/field/password.html b/public/modules/forms/views/directiveViews/field/password.html new file mode 100755 index 00000000..0346e26f --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/password.html @@ -0,0 +1,7 @@ +
+
{{field.title}}:
+
+ + * required +
+
diff --git a/public/modules/forms/views/directiveViews/field/radio.html b/public/modules/forms/views/directiveViews/field/radio.html new file mode 100755 index 00000000..5f5ce716 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/radio.html @@ -0,0 +1,13 @@ +
+
{{field.title}}:
+
+
+ +
+ * required +
+
+
diff --git a/public/modules/forms/views/directiveViews/field/textarea.html b/public/modules/forms/views/directiveViews/field/textarea.html new file mode 100755 index 00000000..d7ed44f8 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/textarea.html @@ -0,0 +1,7 @@ +
+
{{field.title}}:
+
+ + * required +
+
diff --git a/public/modules/forms/views/directiveViews/field/textfield.html b/public/modules/forms/views/directiveViews/field/textfield.html new file mode 100755 index 00000000..72b687c2 --- /dev/null +++ b/public/modules/forms/views/directiveViews/field/textfield.html @@ -0,0 +1,11 @@ +
  • +
    {{field.title}}:
    +
    + + * required +
    +
  • diff --git a/public/modules/forms/views/directiveViews/form/form.html b/public/modules/forms/views/directiveViews/form/form.html new file mode 100755 index 00000000..0133a1b8 --- /dev/null +++ b/public/modules/forms/views/directiveViews/form/form.html @@ -0,0 +1,48 @@ +

    {{ form.form_name }}

    + +
    + +
    +

    {{ form.title }}

    +
    +
    +
    + +
    +
    + + +
    + + + + +
    + + +
    +

    + +

    +
    +
    + +
    +

    Form Successfully submitted

    +


    +
    +

    + +

    +
    + + + +
    \ No newline at end of file diff --git a/public/modules/forms/views/list-forms.client.view.html b/public/modules/forms/views/list-forms.client.view.html new file mode 100644 index 00000000..ef7d24a3 --- /dev/null +++ b/public/modules/forms/views/list-forms.client.view.html @@ -0,0 +1,20 @@ +
    + + +
    + No forms yet, why don't you create one? +
    +
    \ No newline at end of file diff --git a/public/modules/forms/views/view-form-submissions.view.html b/public/modules/forms/views/view-form-submissions.view.html new file mode 100644 index 00000000..7f8fb04c --- /dev/null +++ b/public/modules/forms/views/view-form-submissions.view.html @@ -0,0 +1,31 @@ +
    + + +
    + +
    + + + Created on + + by + + + + +
    \ No newline at end of file diff --git a/public/modules/forms/views/view-form.client.view.html b/public/modules/forms/views/view-form.client.view.html new file mode 100644 index 00000000..8199befb --- /dev/null +++ b/public/modules/forms/views/view-form.client.view.html @@ -0,0 +1,31 @@ +
    + + +
    + +
    + + + Created on + + by + + + + +
    \ No newline at end of file diff --git a/public/modules/forms/views/view-public-form.client.view.html b/public/modules/forms/views/view-public-form.client.view.html new file mode 100644 index 00000000..c48dbe50 --- /dev/null +++ b/public/modules/forms/views/view-public-form.client.view.html @@ -0,0 +1,33 @@ + + +
    +
    + + + +
    + diff --git a/public/modules/users/config/users.client.config.js b/public/modules/users/config/users.client.config.js new file mode 100755 index 00000000..d4756c5b --- /dev/null +++ b/public/modules/users/config/users.client.config.js @@ -0,0 +1,30 @@ +// 'use strict'; + +// // Config HTTP Error Handling +// angular.module('users').config(['$httpProvider', +// function($httpProvider) { +// // Set the httpProvider "not authorized" interceptor +// $httpProvider.interceptors.push(['$q', '$location', 'Principal', +// function($q, $location, Principal) { +// return { +// responseError: function(rejection) { +// switch (rejection.status) { +// case 401: +// // Deauthenticate the global user +// Principal.authenticate(null); + +// // Redirect to signin page +// $location.path('signin'); +// break; +// case 403: +// // Add unauthorized behaviour +// break; +// } + +// return $q.reject(rejection); +// } +// }; +// } +// ]); +// } +// ]); \ No newline at end of file diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js new file mode 100755 index 00000000..40711f41 --- /dev/null +++ b/public/modules/users/config/users.client.routes.js @@ -0,0 +1,63 @@ +'use strict'; + +// Setting up route +angular.module('users').config(['$stateProvider', + function($stateProvider) { + // Users state routing + $stateProvider. + state('profile', { + // parent: 'restricted', + // data: { + // roles: ['user', 'admin'], + // }, + url: '/settings/profile', + templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' + }). + state('password', { + // parent: 'restricted', + // data: { + // roles: ['user', 'admin'], + // }, + url: '/settings/password', + templateUrl: 'modules/users/views/settings/change-password.client.view.html' + }). + state('accounts', { + // parent: 'restricted', + // data: { + // roles: ['user', 'admin'], + // }, + url: '/settings/accounts', + templateUrl: 'modules/users/views/settings/social-accounts.client.view.html' + }). + + state('signup', { + url: '/signup', + templateUrl: 'modules/users/views/authentication/signup.client.view.html' + }). + state('signin', { + url: '/signin', + templateUrl: 'modules/users/views/authentication/signin.client.view.html' + }). + state('access_denied', { + url: '/access_denied', + templateUrl: 'modules/users/views/authentication/access-denied.client.view.html' + }). + + state('forgot', { + url: '/password/forgot', + templateUrl: 'modules/users/views/password/forgot-password.client.view.html' + }). + state('reset-invalid', { + url: '/password/reset/invalid', + templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' + }). + state('reset-success', { + url: '/password/reset/success', + templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' + }). + state('reset', { + url: '/password/reset/:token', + templateUrl: 'modules/users/views/password/reset-password.client.view.html' + }); + } +]); \ No newline at end of file diff --git a/public/modules/users/controllers/authentication.client.controller.js b/public/modules/users/controllers/authentication.client.controller.js new file mode 100755 index 00000000..8c596053 --- /dev/null +++ b/public/modules/users/controllers/authentication.client.controller.js @@ -0,0 +1,51 @@ +'use strict'; + +angular.module('users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Principal', '$state', + function($scope, $http, $location, Principal, $state) { + + $scope.authentication = Principal; + // $scope.authentication.user = Principal.getUser(); + + // If user is signed in then redirect back home + if ($scope.authentication.isAuthenticated()) $state.go('home'); + + $scope.signup = function() { + var response_obj = Principal.signup($scope.credentials); + + if( angular.isDefined(response_obj.error) ){ + $scope.error = response_obj.error; + } else{ + $state.go('home'); + } + // $http.post('/auth/signup', $scope.credentials).success(function(response) { + // // If successful we assign the response to the global user model + // $scope.authentication.user = response; + + // // And redirect to the index page + // $location.path('/'); + // }).error(function(response) { + // $scope.error = response.message; + // }); + }; + + $scope.signin = function() { + console.log('signin'); + var response_obj = Principal.signin($scope.credentials); + if( angular.isDefined(response_obj.error) ){ + $scope.error = response_obj.error; + $location.path('/signin'); + } else{ + $location.path('/'); + } + // $http.post('/auth/signin', $scope.credentials).success(function(response) { + // // If successful we assign the response to the global user model + // $scope.authentication.user = response; + + // // And redirect to the index page + // $location.path('/'); + // }).error(function(response) { + // $scope.error = response.message; + // }); + }; + } +]); \ No newline at end of file diff --git a/public/modules/users/controllers/password.client.controller.js b/public/modules/users/controllers/password.client.controller.js new file mode 100755 index 00000000..3581b2a5 --- /dev/null +++ b/public/modules/users/controllers/password.client.controller.js @@ -0,0 +1,45 @@ +'use strict'; + +angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$state', 'Principal', + function($scope, $stateParams, $http, $state, Principal) { + $scope.authentication = Principal; + $scope.authentication.user = Principal.user(); + + //If user is signed in then redirect back home + if ($scope.authentication.user) $state.go('home'); + + // Submit forgotten password account id + $scope.askForPasswordReset = function() { + $scope.success = $scope.error = null; + + $http.post('/auth/forgot', $scope.credentials).success(function(response) { + // Show user success message and clear form + $scope.credentials = null; + $scope.success = response.message; + + }).error(function(response) { + // Show user error message and clear form + $scope.credentials = null; + $scope.error = response.message; + }); + }; + + // Change user password + $scope.resetUserPassword = function() { + $scope.success = $scope.error = null; + + $http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { + // If successful show success message and clear form + $scope.passwordDetails = null; + + // Attach user profile + // Principal.user() = response; + + // And redirect to the index page + $state.go('reset-success'); + }).error(function(response) { + $scope.error = response.message; + }); + }; + } +]); \ No newline at end of file diff --git a/public/modules/users/controllers/settings.client.controller.js b/public/modules/users/controllers/settings.client.controller.js new file mode 100755 index 00000000..8e463254 --- /dev/null +++ b/public/modules/users/controllers/settings.client.controller.js @@ -0,0 +1,76 @@ +'use strict'; + +angular.module('users').controller('SettingsController', ['$scope', '$http', '$state', 'Users', 'Principal', + function($scope, $http, $state, Users, Principal) { + + Principal.identity().then(function(user){ + $scope.user = user; + }).then(function(){ + + // If user is not signed in then redirect back home + if (!$scope.user) $state.go('home'); + + // Check if there are additional accounts + $scope.hasConnectedAdditionalSocialAccounts = function(provider) { + for (var i in $scope.user.additionalProvidersData) { + return true; + } + + return false; + }; + + // Check if provider is already in use with current user + $scope.isConnectedSocialAccount = function(provider) { + return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); + }; + + // Remove a user social account + $scope.removeUserSocialAccount = function(provider) { + $scope.success = $scope.error = null; + + $http.delete('/users/accounts', { + params: { + provider: provider + } + }).success(function(response) { + // If successful show success message and clear form + $scope.success = true; + $scope.user = response; + }).error(function(response) { + $scope.error = response.message; + }); + }; + + // Update a user profile + $scope.updateUserProfile = function(isValid) { + if (isValid) { + $scope.success = $scope.error = null; + var user = new Users($scope.user); + + user.$update(function(response) { + $scope.success = true; + $scope.user = response; + }, function(response) { + $scope.error = response.data.message; + }); + } else { + $scope.submitted = true; + } + }; + + // Change user password + $scope.changeUserPassword = function() { + $scope.success = $scope.error = null; + + $http.post('/users/password', $scope.passwordDetails).success(function(response) { + // If successful show success message and clear form + $scope.success = true; + $scope.passwordDetails = null; + }).error(function(response) { + $scope.error = response.message; + }); + }; + + }); + } +]); \ No newline at end of file diff --git a/public/modules/users/css/users.css b/public/modules/users/css/users.css new file mode 100755 index 00000000..de67bf94 --- /dev/null +++ b/public/modules/users/css/users.css @@ -0,0 +1,14 @@ +@media (min-width: 992px) { + .nav-users { + position: fixed; + } +} +.remove-account-container { + display: inline-block; + position: relative; +} +.btn-remove-account { + top: 10px; + right: 10px; + position: absolute; +} \ No newline at end of file diff --git a/public/modules/users/img/buttons/facebook.png b/public/modules/users/img/buttons/facebook.png new file mode 100755 index 00000000..8ebea4f7 Binary files /dev/null and b/public/modules/users/img/buttons/facebook.png differ diff --git a/public/modules/users/img/buttons/github.png b/public/modules/users/img/buttons/github.png new file mode 100755 index 00000000..6ff6672f Binary files /dev/null and b/public/modules/users/img/buttons/github.png differ diff --git a/public/modules/users/img/buttons/google.png b/public/modules/users/img/buttons/google.png new file mode 100755 index 00000000..0c007125 Binary files /dev/null and b/public/modules/users/img/buttons/google.png differ diff --git a/public/modules/users/img/buttons/linkedin.png b/public/modules/users/img/buttons/linkedin.png new file mode 100755 index 00000000..ff039a42 Binary files /dev/null and b/public/modules/users/img/buttons/linkedin.png differ diff --git a/public/modules/users/img/buttons/twitter.png b/public/modules/users/img/buttons/twitter.png new file mode 100755 index 00000000..2e399c17 Binary files /dev/null and b/public/modules/users/img/buttons/twitter.png differ diff --git a/public/modules/users/services/authorization.client.service.js b/public/modules/users/services/authorization.client.service.js new file mode 100644 index 00000000..46d79914 --- /dev/null +++ b/public/modules/users/services/authorization.client.service.js @@ -0,0 +1,28 @@ +'use strict'; + +angular.module('users').service('Authorization', ['$rootScope', '$location', 'Principal', + function($rootScope, $location, Principal) { + + this.authorize = function() { + return Principal.identity().then(function(){ + var isAuthenticated = Principal.isAuthenticated(); + if( angular.isDefined($rootScope.toState.data) ){ + // if ($rootScope.toState.data.roles && $rootScope.toState.data.roles.length > 0 && !principal.isInAnyRole($rootScope.toState.data.roles)) { + if (!isAuthenticated){ //$location.path('/access_denied'); // user is signed in but not authorized for desired state + // console.log('isAuthenticated: '+isAuthenticated); + + // else { + // user is not authenticated. so the state they wanted before you + // send them to the signin state, so you can return them when you're done + $rootScope.returnToState = $rootScope.toState; + $rootScope.returnToStateParams = $rootScope.toStateParams; + + // now, send them to the signin state so they can log in + $location.path('/signin'); + } + // } + } + }); + }; + } +]); \ No newline at end of file diff --git a/public/modules/users/services/principal.client.service.js b/public/modules/users/services/principal.client.service.js new file mode 100755 index 00000000..78a22d9e --- /dev/null +++ b/public/modules/users/services/principal.client.service.js @@ -0,0 +1,128 @@ +'use strict'; + +angular.module('users').factory('Principal', ['$window', '$http', '$q', '$timeout', '$state', + function($window, $http, $q, $timeout, $state) { + var _identity, + _authenticated = false; + + return { + isIdentityResolved: function() { + return angular.isDefined(_identity); + }, + isAuthenticated: function() { + return _authenticated; + }, + isInRole: function(role) { + if (!_authenticated || !_identity.roles) return false; + + return _identity.roles.indexOf(role) !== -1; + }, + isInAnyRole: function(roles) { + if (!_authenticated || !_identity.roles) return false; + + for (var i = 0; i < roles.length; i++) { + if (this.isInRole(roles[i])) return true; + } + + return false; + }, + authenticate: function(user) { + _identity = user; + _authenticated = (user !== null); + + // for this demo, we'll store the identity in localStorage. For you, it could be a cookie, sessionStorage, whatever + if (user) $window.user = user; + else $window.user = null; + }, + signin: function(credentials) { + + var deferred = $q.defer(); + var self = this; + $http.post('/auth/signin', credentials).success(function(response) { + // If successful we assign the response to the global user model + self.authenticate(response); + deferred.resolve(response); + }).error(function(response) { + _authenticated = false; + deferred.resolve({ error: response.message }); + }); + return deferred.promise; + }, + signup: function(credentials) { + + var deferred = $q.defer(); + + $http.post('/auth/signup', credentials).success(function(response) { + // If successful we assign the response to the global user model + deferred.resolve(response); + }).error(function(response) { + + deferred.resolve({ error: response.message }); + }); + + return deferred.promise; + }, + signout: function() { + var deferred = $q.defer(); + $http.get('/auth/signout').success(function(response) { + // If successful we assign the response to the global user model + deferred.resolve({}); + }).error(function(response) { + deferred.resolve({ error: response.message }); + }); + + _authenticated = false; + _identity = undefined; + + return deferred.promise; + }, + identity: function(force) { + var self = this; + + var deferred = $q.defer(); + + if (force === true) _identity = undefined; + + // check and see if we have retrieved the user data from the server. if we have, reuse it by immediately resolving + if (angular.isDefined(_identity)) { + + deferred.resolve(_identity); + return deferred.promise; + }else if($window.user){ + // console.log($window.user); + // self.authenticate($window.user); + // var user = $window.user; + _identity = $window.user; + self.authenticate(_identity); + deferred.resolve(_identity); + + return deferred.promise; + }else { + + // otherwise, retrieve the user data from the server, update the user object, and then resolve. + $http.get('/users/me', { ignoreErrors: true }) + .success(function(response) { + self.authenticate(response); + $window.user = response; + deferred.resolve(_identity); + }) + .error(function() { + _identity = null; + _authenticated = false; + $window.user = null; + $state.path('signin'); + deferred.resolve(_identity); + }); + + return deferred.promise; + } + }, + getUser: function(){ + this.identity(false).then( function(user){ + return user; + }); + } + }; + + } +]); diff --git a/public/modules/users/services/users.client.service.js b/public/modules/users/services/users.client.service.js new file mode 100755 index 00000000..664828f0 --- /dev/null +++ b/public/modules/users/services/users.client.service.js @@ -0,0 +1,12 @@ +'use strict'; + +// Users service used for communicating with the users REST endpoint +angular.module('users').factory('Users', ['$resource', + function($resource) { + return $resource('users', {}, { + update: { + method: 'PUT' + } + }); + } +]); \ No newline at end of file diff --git a/public/modules/users/tests/authentication.client.controller.test.js b/public/modules/users/tests/authentication.client.controller.test.js new file mode 100755 index 00000000..3b4e05d6 --- /dev/null +++ b/public/modules/users/tests/authentication.client.controller.test.js @@ -0,0 +1,118 @@ +'use strict'; + +(function() { + // Principal controller Spec + describe('AuthenticationController', function() { + // Initialize global variables + var AuthenticationController, + scope, + $httpBackend, + $stateParams, + $location; + + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). + // This allows us to inject a service but then attach it to a variable + // with the same name as the service. + beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { + // Set a new global scope + scope = $rootScope.$new(); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + + // Initialize the Principal controller + AuthenticationController = $controller('AuthenticationController', { + $scope: scope + }); + })); + + + it('$scope.signin() should login with a correct user and password', function() { + // Test expected GET request + $httpBackend.when('POST', '/auth/signin').respond(200, 'Fred'); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.authentication.user).toEqual('Fred'); + expect($location.url()).toEqual('/'); + }); + + it('$scope.signin() should fail to log in with nothing', function() { + // Test expected POST request + $httpBackend.expectPOST('/auth/signin').respond(400, { + 'message': 'Missing credentials' + }); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toEqual('Missing credentials'); + }); + + it('$scope.signin() should fail to log in with wrong credentials', function() { + // Foo/Bar combo assumed to not exist + scope.authentication.user = 'Foo'; + scope.credentials = 'Bar'; + + // Test expected POST request + $httpBackend.expectPOST('/auth/signin').respond(400, { + 'message': 'Unknown user' + }); + + scope.signin(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toEqual('Unknown user'); + }); + + it('$scope.signup() should register with correct data', function() { + // Test expected GET request + scope.authentication.user = 'Fred'; + $httpBackend.when('POST', '/auth/signup').respond(200, 'Fred'); + + scope.signup(); + $httpBackend.flush(); + + // test scope value + expect(scope.authentication.user).toBe('Fred'); + expect(scope.error).toEqual(undefined); + expect($location.url()).toBe('/'); + }); + + it('$scope.signup() should fail to register with duplicate Username', function() { + // Test expected POST request + $httpBackend.when('POST', '/auth/signup').respond(400, { + 'message': 'Username already exists' + }); + + scope.signup(); + $httpBackend.flush(); + + // Test scope value + expect(scope.error).toBe('Username already exists'); + }); + }); +}()); \ No newline at end of file diff --git a/public/modules/users/users.client.module.js b/public/modules/users/users.client.module.js new file mode 100755 index 00000000..7b2f6465 --- /dev/null +++ b/public/modules/users/users.client.module.js @@ -0,0 +1,4 @@ +'use strict'; + +// Use Application configuration module to register a new module +ApplicationConfiguration.registerModule('users'); \ No newline at end of file diff --git a/public/modules/users/views/authentication/access-denied.client.view.html b/public/modules/users/views/authentication/access-denied.client.view.html new file mode 100644 index 00000000..09317657 --- /dev/null +++ b/public/modules/users/views/authentication/access-denied.client.view.html @@ -0,0 +1,4 @@ +
    +

    You need to be logged in to access this page

    + Login +
    \ No newline at end of file diff --git a/public/modules/users/views/authentication/signin.client.view.html b/public/modules/users/views/authentication/signin.client.view.html new file mode 100755 index 00000000..e56e87de --- /dev/null +++ b/public/modules/users/views/authentication/signin.client.view.html @@ -0,0 +1,45 @@ +
    +

    Sign in with your account

    + + +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/authentication/signup.client.view.html b/public/modules/users/views/authentication/signup.client.view.html new file mode 100755 index 00000000..e2051760 --- /dev/null +++ b/public/modules/users/views/authentication/signup.client.view.html @@ -0,0 +1,54 @@ +
    +

    Sign up using your social accounts

    + +

    Or with your email

    +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/password/forgot-password.client.view.html b/public/modules/users/views/password/forgot-password.client.view.html new file mode 100755 index 00000000..e6275f94 --- /dev/null +++ b/public/modules/users/views/password/forgot-password.client.view.html @@ -0,0 +1,22 @@ +
    +

    Restore your password

    +

    Enter your account username.

    +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/password/reset-password-invalid.client.view.html b/public/modules/users/views/password/reset-password-invalid.client.view.html new file mode 100755 index 00000000..d5fc2373 --- /dev/null +++ b/public/modules/users/views/password/reset-password-invalid.client.view.html @@ -0,0 +1,4 @@ +
    +

    Password reset is invalid

    + Ask for a new password reset +
    \ No newline at end of file diff --git a/public/modules/users/views/password/reset-password-success.client.view.html b/public/modules/users/views/password/reset-password-success.client.view.html new file mode 100755 index 00000000..4de46c4b --- /dev/null +++ b/public/modules/users/views/password/reset-password-success.client.view.html @@ -0,0 +1,4 @@ +
    +

    Password successfully reset

    + Continue to home page +
    \ No newline at end of file diff --git a/public/modules/users/views/password/reset-password.client.view.html b/public/modules/users/views/password/reset-password.client.view.html new file mode 100755 index 00000000..dc8b2ea0 --- /dev/null +++ b/public/modules/users/views/password/reset-password.client.view.html @@ -0,0 +1,26 @@ +
    +

    Reset your password

    +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/settings/change-password.client.view.html b/public/modules/users/views/settings/change-password.client.view.html new file mode 100755 index 00000000..26d59e94 --- /dev/null +++ b/public/modules/users/views/settings/change-password.client.view.html @@ -0,0 +1,30 @@ +
    +

    Change your password

    +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/settings/edit-profile.client.view.html b/public/modules/users/views/settings/edit-profile.client.view.html new file mode 100755 index 00000000..9820af9e --- /dev/null +++ b/public/modules/users/views/settings/edit-profile.client.view.html @@ -0,0 +1,34 @@ +
    +

    Edit your profile

    +
    + +
    +
    \ No newline at end of file diff --git a/public/modules/users/views/settings/social-accounts.client.view.html b/public/modules/users/views/settings/social-accounts.client.view.html new file mode 100755 index 00000000..4712ee09 --- /dev/null +++ b/public/modules/users/views/settings/social-accounts.client.view.html @@ -0,0 +1,29 @@ +
    +

    Connected social accounts:

    +
    + +
    +

    Connect other social accounts:

    + +
    \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100755 index 00000000..ee2cc216 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# robotstxt.org/ + +User-agent: * diff --git a/scripts/generate-ssl-certs.sh b/scripts/generate-ssl-certs.sh new file mode 100755 index 00000000..f09002c0 --- /dev/null +++ b/scripts/generate-ssl-certs.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ ! -e server.js ] +then + echo "Error: could not find main application server.js file" + echo "You should run the generate-ssl-certs.sh script from the main MEAN application root directory" + echo "i.e: bash scripts/generate-ssl-certs.sh" + exit -1 +fi + +echo "Generating self-signed certificates..." +mkdir -p ./config/sslcerts +openssl genrsa -out ./config/sslcerts/key.pem -aes256 1024 +openssl req -new -key ./config/sslcerts/key.pem -out ./config/sslcerts/csr.pem +openssl x509 -req -days 9999 -in ./config/sslcerts/csr.pem -signkey ./config/sslcerts/key.pem -out ./config/sslcerts/cert.pem +rm ./config/sslcerts/csr.pem +chmod 600 ./config/sslcerts/key.pem ./config/sslcerts/cert.pem diff --git a/server.js b/server.js new file mode 100755 index 00000000..6d5f4c2d --- /dev/null +++ b/server.js @@ -0,0 +1,49 @@ +'use strict'; +/** + * Module dependencies. + */ +var init = require('./config/init')(), + config = require('./config/config'), + mongoose = require('mongoose'), + chalk = require('chalk'); + +/** + * Main application entry file. + * Please note that the order of loading is important. + */ + +// Bootstrap db connection +var db = mongoose.connect(config.db.uri, config.db.options, function(err) { + if (err) { + console.error(chalk.red('Could not connect to MongoDB!')); + console.log(chalk.red(err)); + } +}); +mongoose.connection.on('error', function(err) { + console.error(chalk.red('MongoDB connection error: ' + err)); + process.exit(-1); + } +); + +// Init the express application +var app = require('./config/express')(db); + +// Bootstrap passport config +require('./config/passport')(); + +// Start the app by listening on +app.listen(config.port); + +// Expose app +exports = module.exports = app; + +// Logging initialization +console.log('--'); +console.log(chalk.green(config.app.title + ' application started')); +console.log(chalk.green('Environment:\t\t\t' + process.env.NODE_ENV)); +console.log(chalk.green('Port:\t\t\t\t' + config.port)); +console.log(chalk.green('Database:\t\t\t' + config.db.uri)); +if (process.env.NODE_ENV === 'secure') { + console.log(chalk.green('HTTPs:\t\t\t\ton')); +} +console.log('--'); diff --git a/uploads/pdfs/snthsnthsnth_1435613023097_submission.pdf b/uploads/pdfs/snthsnthsnth_1435613023097_submission.pdf new file mode 100644 index 00000000..8103a95b Binary files /dev/null and b/uploads/pdfs/snthsnthsnth_1435613023097_submission.pdf differ diff --git a/uploads/tmp/1435325338602.pdf b/uploads/tmp/1435325338602.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435325338602.pdf differ diff --git a/uploads/tmp/1435325714724.pdf b/uploads/tmp/1435325714724.pdf new file mode 100644 index 00000000..e090513d Binary files /dev/null and b/uploads/tmp/1435325714724.pdf differ diff --git a/uploads/tmp/1435325906672.pdf b/uploads/tmp/1435325906672.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435325906672.pdf differ diff --git a/uploads/tmp/1435326011705.pdf b/uploads/tmp/1435326011705.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326011705.pdf differ diff --git a/uploads/tmp/1435326012438.pdf b/uploads/tmp/1435326012438.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012438.pdf differ diff --git a/uploads/tmp/1435326012465.pdf b/uploads/tmp/1435326012465.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012465.pdf differ diff --git a/uploads/tmp/1435326012488.pdf b/uploads/tmp/1435326012488.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012488.pdf differ diff --git a/uploads/tmp/1435326012575.pdf b/uploads/tmp/1435326012575.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012575.pdf differ diff --git a/uploads/tmp/1435326012597.pdf b/uploads/tmp/1435326012597.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012597.pdf differ diff --git a/uploads/tmp/1435326012626.pdf b/uploads/tmp/1435326012626.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326012626.pdf differ diff --git a/uploads/tmp/1435326404877.pdf b/uploads/tmp/1435326404877.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326404877.pdf differ diff --git a/uploads/tmp/1435326407158.pdf b/uploads/tmp/1435326407158.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326407158.pdf differ diff --git a/uploads/tmp/1435326407195.pdf b/uploads/tmp/1435326407195.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326407195.pdf differ diff --git a/uploads/tmp/1435326407338.pdf b/uploads/tmp/1435326407338.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326407338.pdf differ diff --git a/uploads/tmp/1435326408591.pdf b/uploads/tmp/1435326408591.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326408591.pdf differ diff --git a/uploads/tmp/1435326409876.pdf b/uploads/tmp/1435326409876.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326409876.pdf differ diff --git a/uploads/tmp/1435326410139.pdf b/uploads/tmp/1435326410139.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326410139.pdf differ diff --git a/uploads/tmp/1435326731947.pdf b/uploads/tmp/1435326731947.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326731947.pdf differ diff --git a/uploads/tmp/1435326852103.pdf b/uploads/tmp/1435326852103.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435326852103.pdf differ diff --git a/uploads/tmp/1435612620851.pdf b/uploads/tmp/1435612620851.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435612620851.pdf differ diff --git a/uploads/tmp/1435612633548.pdf b/uploads/tmp/1435612633548.pdf new file mode 100644 index 00000000..b93e7316 Binary files /dev/null and b/uploads/tmp/1435612633548.pdf differ