Merge branch '2.20' into fixSlowAnalyticsBug

This commit is contained in:
David Baldwynn 2017-11-06 14:47:01 -08:00 committed by GitHub
commit 04e71be14a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1878 additions and 604 deletions

View file

@ -10,7 +10,13 @@ var mongoose = require('mongoose'),
config = require('../../config/config'),
diff = require('deep-diff'),
_ = require('lodash'),
helpers = require('./helpers.server.controller');
nodemailer = require('nodemailer'),
emailNotifications = require('../libs/send-email-notifications'),
constants = require('../libs/constants'),
helpers = require('./helpers.server.controller'),
async = require('async');
var smtpTransport = nodemailer.createTransport(config.mailer.options);
/**
* Delete a forms submissions
@ -70,7 +76,51 @@ exports.createSubmission = function(req, res) {
message: errorHandler.getErrorMessage(err)
});
}
res.status(200).send('Form submission successfully saved');
var form = req.body
var formFieldDict = emailNotifications.createFieldDict(form.form_fields);
async.waterfall([
function(callback) {
if (form.selfNotifications && form.selfNotifications.enabled && form.selfNotifications.fromField) {
form.selfNotifications.fromEmails = formFieldDict[form.selfNotifications.fromField];
emailNotifications.send(form.selfNotifications, formFieldDict, smtpTransport, constants.varFormat, function(err){
if(err){
return callback({
message: 'Failure sending submission self-notification email'
});
}
callback();
});
} else {
callback();
}
},
function(callback) {
if (form.respondentNotifications && form.respondentNotifications.enabled && form.respondentNotifications.toField) {
form.respondentNotifications.toEmails = formFieldDict[form.respondentNotifications.toField];
debugger;
emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, constants.varFormat, function(err){
if(err){
return callback({
message: 'Failure sending submission respondent-notification email'
});
}
callback();
});
} else {
callback();
}
}
], function (err) {
if(err){
return res.status(400).send(err);
}
res.status(200).send('Form submission successfully saved');
});
});
};
@ -224,14 +274,13 @@ exports.getVisitorData = function(req, res) {
* Create a new form
*/
exports.create = function(req, res) {
if(!req.body.form){
return res.status(400).send({
message: 'Invalid Input'
});
}
var form = new Form(req.body.form);
form.admin = req.user._id;
form.save(function(err, createdForm) {
@ -302,7 +351,7 @@ exports.update = function(req, res) {
form.analytics = {
visitors: [],
gaCode: ''
}
};
}
if (req.body.changes) {
@ -432,7 +481,7 @@ exports.formByIDFast = function(req, res, next, id) {
}
Form.findById(id)
.lean()
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode')
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode selfNotifications respondentNotifications')
.exec(function(err, form) {
if (err) {
return next(err);

View file

@ -1,3 +1,5 @@
'use strict';
module.exports = {
removeSensitiveModelData: function(type, object){
var privateFields = {
@ -5,7 +7,7 @@ module.exports = {
'private_form': ['__v'],
'public_user': ['passwordHash', 'password', 'provider', 'salt', 'lastModified', 'created', 'resetPasswordToken', 'resetPasswordExpires', 'token', 'apiKey', '__v'],
'private_user': ['passwordHash', 'password', 'provider', 'salt', 'resetPasswordToken', 'resetPasswordExpires', 'token', '__v']
}
};
function removeKeysFromDict(dict, keys){
for(var i=0; i<keys.length; i++){
@ -16,19 +18,18 @@ module.exports = {
}
}
switch(type){
case 'private_form':
removeKeysFromDict(object, privateFields['private_form']);
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields['private_user']);
removeKeysFromDict(object.admin, privateFields.private_user);
}
break;
case 'public_form':
removeKeysFromDict(object, privateFields['public_form']);
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields['public_user']);
removeKeysFromDict(object.admin, privateFields.public_user);
}
break;
@ -41,4 +42,4 @@ module.exports = {
return object;
}
}
};

View file

@ -61,7 +61,7 @@ config_nev();
exports.validateVerificationToken = function(req, res){
const fn = pug.compileFile(__dirname + "/../../views/welcome.email.view.pug");
const fn = pug.compileFile(__dirname + '/../../views/welcome.email.view.pug');
var renderedHtml = fn(res.locals);
var emailTemplate = {
@ -84,7 +84,7 @@ exports.validateVerificationToken = function(req, res){
};
exports.resendVerificationEmail = function(req, res, next){
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
var renderedHtml = fn(res.locals);
var emailTemplate = {
@ -118,7 +118,7 @@ exports.signup = function(req, res) {
var user = new User(req.body);
// Set language to visitor's language
user.language = req.cookies['userLang'];
user.language = req.cookies.userLang;
// Add missing user fields
user.provider = 'local';
@ -134,7 +134,7 @@ exports.signup = function(req, res) {
// new user created
if (newTempUser) {
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
var renderedHtml = fn(res.locals);
var URL = newTempUser[nev.options.URLFieldName];

View file

@ -81,8 +81,8 @@ exports.forgot = function(req, res) {
}
},
function(token, user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-email.server.view.pug");
res.locals['url'] = 'http://' + req.headers.host + '/auth/reset/' + token;
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-email.server.view.pug');
res.locals.url = 'http://' + req.headers.host + '/auth/reset/' + token;
var renderedHtml = fn(res.locals);
done(null, renderedHtml, user);
@ -97,10 +97,10 @@ exports.forgot = function(req, res) {
};
var userEmail = user.email;
var user = userEmail.split('@')[0];
var emailUsername = userEmail.split('@')[0];
var domain = userEmail.split('@')[1];
var obfuscatedUser = user.substring(0, 1) + user.substring(1).replace(/./g, '*');
var obfuscatedUser = emailUsername.substring(0, 1) + emailUsername.substring(1).replace(/./g, '*');
var domainName = domain.split('.')[0];
var tld = domain.split('.')[1];
@ -191,8 +191,8 @@ exports.reset = function(req, res, next) {
});
},
function(user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-confirm-email.server.view.pug");
var renderedHtml = fn(res.locals);
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-confirm-email.server.view.pug');
const renderedHtml = fn(res.locals);
done(null, renderedHtml, user);
},
// If valid email, send reset email using service

View file

@ -4,7 +4,6 @@ module.exports = {
fieldTypes: ['textfield',
'date',
'email',
'link',
'legal',
'url',
'textarea',
@ -74,6 +73,9 @@ module.exports = {
regex: {
url: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/,
hexCode: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
email: /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
}
email: /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
templateVariable: /<var(.*)id(.*)>(.|\n)*?<\/var>/g
},
varFormat: ['<var([^<>]+)id=["\']{1}field:', '["\']{1}>([^<>]+)*?<\/var>'],
};

View file

@ -0,0 +1,44 @@
'use strict';
module.exports = {
send: function(emailSettings, emailTemplateVars, smtpTransport, varFormat, cb){
var parsedTemplate = this.parseTemplate(emailSettings.htmlTemplate, emailTemplateVars, varFormat);
var parsedSubject = this.parseTemplate(emailSettings.subject, emailTemplateVars, varFormat);
var mailOptions = {
replyTo: emailSettings.fromEmails,
from: 'noreply@tellform.com',
cc: emailSettings.toEmails,
subject: parsedSubject,
html: parsedTemplate
};
console.log('HERE');
smtpTransport.sendMail(mailOptions, function(){
console.log('THERE');
cb();
});
},
parseTemplate: function(emailTemplate, emailAttrs, varFormat){
var resolvedTemplate = emailTemplate;
var that = this;
Object.keys(emailAttrs).forEach(function (key) {
resolvedTemplate = that.replaceTemplateVal(key, emailAttrs[key], resolvedTemplate, varFormat);
});
return resolvedTemplate;
},
replaceTemplateVal: function(key, val, template, varFormat){
return template.replace( new RegExp(varFormat[0] + key + varFormat[1], 'g'), val);
},
createFieldDict: function(form_fields){
var formFieldDict = {};
form_fields.forEach(function(field){
if(field.hasOwnProperty('globalId') && field.hasOwnProperty('fieldValue')){
formFieldDict[field.globalId] = field.fieldValue;
}
});
return formFieldDict;
}
};

View file

@ -2,38 +2,36 @@
// Plugin
module.exports = function timestamp (schema, options) {
options || (options = {})
options = options || (options === {});
// Options
var fields = {}
, createdPath = options.createdPath || 'created'
, modifiedPath = options.modifiedPath || 'modified'
, useVirtual = (options.useVirtual !== undefined)
? options.useVirtual
: true
var fields = {},
createdPath = options.createdPath || 'created',
modifiedPath = options.modifiedPath || 'modified',
useVirtual = (options.useVirtual !== undefined) ? options.useVirtual : true;
// Add paths to schema if not present
if (!schema.paths[createdPath]) {
fields[modifiedPath] = { type: Date }
fields[modifiedPath] = { type: Date };
}
if (useVirtual) {
// Use the ObjectID for extracting the created time
schema.virtual(createdPath).get(function () {
return new Date(this._id.generationTime * 1000)
})
return new Date(this._id.generationTime * 1000);
});
} else {
if (!schema.paths[createdPath]) {
fields[createdPath] = {
type: Date
, default: Date.now
}
type: Date,
default: Date.now
};
}
}
schema.add(fields)
schema.add(fields);
// Update the modified timestamp on save
schema.pre('save', function (next) {
this[modifiedPath] = new Date
next()
})
}
this[modifiedPath] = new Date();
next();
});
};

View file

@ -7,7 +7,8 @@ var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash'),
timeStampPlugin = require('../libs/timestamp.server.plugin'),
async = require('async');
async = require('async'),
constants = require('../libs/constants');
//Mongoose Models
var FieldSchema = require('./form_field.server.model.js');
@ -52,11 +53,12 @@ var VisitorDataSchema = new Schema({
type: Boolean
},
language: {
type: String
type: String,
enum: constants.languageTypes,
default: 'en',
},
ipAddr: {
type: String,
default: ''
type: String
},
deviceType: {
type: String,
@ -149,6 +151,47 @@ var FormSchema = new Schema({
buttons:[ButtonSchema]
},
selfNotifications: {
fromField: {
type: String
},
toEmails: {
type: String
},
subject: {
type: String
},
htmlTemplate: {
type: String
},
enabled: {
type: Boolean,
default: false
}
},
respondentNotifications: {
toField: {
type: String
},
fromEmails: {
type: String,
match: [/.+\@.+\..+/, 'Please fill a valid email address']
},
subject: {
type: String,
default: 'Tellform: Thank you for filling out this TellForm'
},
htmlTemplate: {
type: String,
default: 'Hello, <br><br> Weve received your submission. <br><br> Thank you & have a nice day!',
},
enabled: {
type: Boolean,
default: false
}
},
hideFooter: {
type: Boolean,
default: false

View file

@ -9,7 +9,8 @@ var mongoose = require('mongoose'),
_ = require('lodash'),
Schema = mongoose.Schema,
LogicJumpSchema = require('./logic_jump.server.model'),
tokgen = require('../libs/tokenGenerator');
tokgen = require('../libs/tokenGenerator'),
constants = require('../libs/constants');
var FieldOptionSchema = new Schema({
option_id: {
@ -34,21 +35,7 @@ var RatingFieldSchema = new Schema({
},
shape: {
type: String,
enum: [
'Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'
]
enum: constants.ratingShapeTypes
},
validShapes: {
type: [String]
@ -103,29 +90,7 @@ function BaseFieldSchema(){
},
fieldType: {
type: String,
enum: [
'textfield',
'date',
'email',
'link',
'legal',
'url',
'textarea',
'statement',
'welcome',
'thankyou',
'file',
'dropdown',
'scale',
'rating',
'radio',
'checkbox',
'hidden',
'yes_no',
'natural',
'stripe',
'number'
]
enum: constants.fieldTypes
},
fieldValue: Schema.Types.Mixed
});
@ -140,7 +105,7 @@ function BaseFieldSchema(){
this.validFieldTypes = mongoose.model('Field').schema.path('fieldType').enumValues;
if(this.fieldType === 'rating' && this.ratingOptions.validShapes.length === 0){
this.ratingOptions.validShapes = mongoose.model('RatingOptions').schema.path('shape').enumValues;
this.ratingOptions.validShapes = constants.ratingShapeTypes;
}
next();

View file

@ -55,18 +55,18 @@ FormSubmissionSchema.pre('save', function (next) {
this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value;
}
delete form_fields[i].validFieldTypes;
delete form_fields[i].disabled;
delete form_fields[i].required;
delete form_fields[i].isSubmission;
delete form_fields[i].title;
delete form_fields[i].fieldOptions;
delete form_fields[i].ratingOptions;
delete form_fields[i].logicJump;
delete form_fields[i].description;
delete form_fields[i].created;
delete form_fields[i].lastModified;
delete form_fields[i].deletePreserved;
delete this.form_fields[i].validFieldTypes;
delete this.form_fields[i].disabled;
delete this.form_fields[i].required;
delete this.form_fields[i].isSubmission;
delete this.form_fields[i].title;
delete this.form_fields[i].fieldOptions;
delete this.form_fields[i].ratingOptions;
delete this.form_fields[i].logicJump;
delete this.form_fields[i].description;
delete this.form_fields[i].created;
delete this.form_fields[i].lastModified;
delete this.form_fields[i].deletePreserved;
}
next();
});

View file

@ -30,7 +30,7 @@ var UserSchema = new Schema({
trim: true,
lowercase: true,
unique: 'Account already exists with this email',
match: [/.+\@.+\..+/, 'Please fill a valid email address'],
match: [constants.regex.email, 'Please fill a valid email address'],
required: [true, 'Email is required']
},
username: {
@ -54,13 +54,13 @@ var UserSchema = new Schema({
roles: {
type: [{
type: String,
enum: ['user', 'admin', 'superuser']
enum: constants.userRoleTypes
}],
default: ['user']
},
language: {
type: String,
enum: ['en', 'fr', 'es', 'it', 'de'],
enum: constants.languageTypes,
default: 'en',
},
lastModified: {

View file

@ -31,7 +31,7 @@ module.exports = function(app) {
}
app.route('/forms/:formIdFast([a-zA-Z0-9]+)')
.post(forms.createSubmission)
.post(forms.createSubmission);
app.route('/forms')
.get(auth.isAuthenticatedOrApiKey, forms.list)

View file

@ -166,7 +166,7 @@ describe('Form Routes Unit tests', function() {
it(' > should not be able to create a Form if body is empty', function(done) {
loginSession.post('/forms')
.send({form: null})
.expect(400, {"message":"Invalid Input"})
.expect(400, {'message':'Invalid Input'})
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);

View file

@ -81,8 +81,8 @@ describe('FormSubmission Model Unit Tests:', function() {
user = new User({
firstName: 'Full',
lastName: 'Name',
email: 'test1@test.com'+Date.now(),
username: 'test1'+Date.now(),
email: 'test1@test.com',
username: 'test1',
password: 'password',
provider: 'local'
});

View file

@ -21,15 +21,14 @@ var credentials, user;
* Form routes tests
*/
describe('Form Submission Routes Unit tests', function() {
var FormObj, _Submission, submissionSession, _SubmissionBody
var FormObj, _Submission, submissionSession, _SubmissionBody;
beforeEach(function(done) {
// Create user credentials
credentials = {
email: 'test@test.com',
username: 'test',
email: 'test423@test.com',
username: 'test534',
password: 'password'
};
@ -45,7 +44,10 @@ describe('Form Submission Routes Unit tests', function() {
// Save a user to the test db and create new Form
user.save(function(err) {
if(err) return done(err);
if(err) {
return done(err);
}
FormObj = new Form({
title: 'Form Title',
language: 'en',
@ -54,7 +56,22 @@ describe('Form Submission Routes Unit tests', function() {
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''})
]
],
selfNotifications: {
fromField: mongoose.Types.ObjectId(),
toEmails: 'john@smith.com',
subject: 'Hello there',
htmlTemplate: '<p> A form was submitted </p>',
enabled: true
},
respondentNotifications: {
toField: mongoose.Types.ObjectId(),
fromEmails: 'john@smith.com',
subject: 'Tellform: Thank you for filling out this TellForm',
htmlTemplate:'Hello, <br><br> Weve received your submission. <br><br> Thank you & have a nice day!',
enabled: true
}
});
FormObj.save(function(formSaveErr, form) {
@ -237,6 +254,4 @@ describe('Form Submission Routes Unit tests', function() {
});
});
});
});

View file

@ -0,0 +1,94 @@
'use strict';
/**
* Module dependencies.
*/
const emailNotifications = require('../../libs/send-email-notifications'),
constants = require('../../libs/constants'),
mockTransport = require("nodemailer").createTransport("Stub"),
config = require('../../../config/config');
/**
* Globals
*/
const validFormFields = [
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, globalId:'56340745f59a6fc9e22028e9'},
{fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, globalId:'5c9e22028e907634f45f59a6'},
{fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, globalId:'56e90745f5934fc9e22028a6'}
];
const validFieldDict = {
'56340745f59a6fc9e22028e9': 'John Smith',
'5c9e22028e907634f45f59a6': 'https://johnsmith.me',
'56e90745f5934fc9e22028a6': 45
};
const invalidFormFields = [
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
{fieldType:'link', title:'Your Website', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
{fieldType:'number', title:'Your Age'}
];
const htmlTemplate = '<p><var class="tag" id="field:56340745f59a6fc9e22028e9">First Name</var> \
<br><var class="tag" id="field:5c9e22028e907634f45f59a6">Your Website</var> \
<br><var class="tag" id="field:56e90745f5934fc9e22028a6">Your Age</var></p>';
const renderedTemplate = '<p>John Smith \
<br>https://johnsmith.me \
<br>45</p>';
/**
* Unit tests
*/
describe('Send Email Notification Unit Tests', function() {
describe('Method createFieldDict', function() {
it('should be return a fieldDict from valid form fields', function() {
var actualFieldDict = emailNotifications.createFieldDict(validFormFields);
actualFieldDict.should.deepEqual(validFieldDict);
});
it('should return empty object if form fields are invalid or empty ', function() {
var actualFieldDict = emailNotifications.createFieldDict(invalidFormFields);
actualFieldDict.should.be.empty();
});
});
describe('Method parseTemplate', function(){
it('should properly render a template given a valid field dict', function() {
var actualRenderedTemplate = emailNotifications.parseTemplate(htmlTemplate, validFieldDict, constants.varFormat).replace((/ |\r\n|\n|\r|\t/gm),'');
actualRenderedTemplate.should.equal(renderedTemplate.replace((/ |\r\n|\n|\r|\t/gm),''));
});
});
describe('Method replaceTemplateVal', function() {
it('should properly replace a template var in a valid template', function() {
var expectedHtml = '<p>John Smith \
<br><var class="tag" id="field:5c9e22028e907634f45f59a6">Your Website</var> \
<br><var class="tag" id="field:56e90745f5934fc9e22028a6">Your Age</var></p>';
var actualRenderedTemplate = emailNotifications.replaceTemplateVal('56340745f59a6fc9e22028e9', validFieldDict['56340745f59a6fc9e22028e9'], htmlTemplate, constants.varFormat).replace((/ |\r\n|\n|\r|\t/gm),'');
actualRenderedTemplate.should.equal(expectedHtml.replace((/ |\r\n|\n|\r|\t/gm),''));
});
});
describe('Method send', function() {
this.timeout(10000);
const emailSettings = {
fromEmails: 'somewhere@somewhere.com',
toEmails: 'there@there.com',
subject: 'Hello <var class="tag" id="field:56340745f59a6fc9e22028e9">First Name</var>!',
htmlTemplate: htmlTemplate
};
const emailTemplateVars = validFieldDict;
const varFormat = constants.varFormat;
it('should properly replace a template var in a valid template', function(done) {
emailNotifications.send(emailSettings, emailTemplateVars, mockTransport, varFormat, function(err){
should.not.exist(err);
done();
});
});
});
});

View file

@ -1,70 +1,72 @@
'use strict';
// Dependencies
var util = require('util')
, assert = require('assert')
, mongoose = require('mongoose')
, timestamp = require('../../libs/timestamp.server.plugin')
, Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
var util = require('util'),
assert = require('assert'),
mongoose = require('mongoose'),
timestamp = require('../../libs/timestamp.server.plugin'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
// Run tests
describe('Timestamp', function () {
describe('#default()', function () {
var FooSchema = new Schema()
FooSchema.plugin(timestamp)
var FooModel = mongoose.model('timeFoo', FooSchema)
, bar = new FooModel()
var FooSchema = new Schema();
FooSchema.plugin(timestamp);
var FooModel = mongoose.model('timeFoo', FooSchema),
bar = new FooModel();
before(function () {
FooModel.remove(function (err) {
assert.strictEqual(err, null)
})
})
assert.strictEqual(err, null);
});
});
it('should have custom properties', function (done) {
assert.strictEqual(typeof FooSchema.virtuals.created, 'object')
assert.strictEqual(typeof FooSchema.paths.modified, 'object')
done()
})
assert.strictEqual(typeof FooSchema.virtuals.created, 'object');
assert.strictEqual(typeof FooSchema.paths.modified, 'object');
done();
});
it('should create the default attributes', function (done) {
bar.save(function (err, doc) {
assert.strictEqual(err, null)
assert.strictEqual(util.isDate(doc.created), true)
assert.strictEqual(util.isDate(doc.modified), true)
done()
})
})
})
assert.strictEqual(err, null);
assert.strictEqual(util.isDate(doc.created), true);
assert.strictEqual(util.isDate(doc.modified), true);
done();
});
});
});
describe('#custom()', function () {
var FooSchema = new Schema()
var FooSchema = new Schema();
FooSchema.plugin(timestamp, {
createdPath: 'oh'
, modifiedPath: 'hai'
, useVirtual: false
})
var BarModel = mongoose.model('timeBar', FooSchema)
, bar = new BarModel()
createdPath: 'oh',
modifiedPath: 'hai',
useVirtual: false
});
var BarModel = mongoose.model('timeBar', FooSchema),
bar = new BarModel();
before(function () {
BarModel.remove(function (err) {
assert.strictEqual(err, null)
})
})
assert.strictEqual(err, null);
});
});
it('should have custom properties', function (done) {
assert.strictEqual(typeof FooSchema.paths.oh, 'object')
assert.strictEqual(typeof FooSchema.paths.hai, 'object')
done()
})
assert.strictEqual(typeof FooSchema.paths.oh, 'object');
assert.strictEqual(typeof FooSchema.paths.hai, 'object');
done();
});
it('should create custom attributes', function (done) {
bar.save(function (err, doc) {
assert.strictEqual(err, null)
assert.strictEqual(util.isDate(doc.oh), true)
assert.strictEqual(util.isDate(doc.hai), true)
done()
})
})
})
})
assert.strictEqual(err, null);
assert.strictEqual(util.isDate(doc.oh), true);
assert.strictEqual(util.isDate(doc.hai), true);
done();
});
});
});
});

View file

@ -48,7 +48,7 @@ describe('User CRUD tests', function() {
.send(_User)
.expect(200)
.end(function(err) {
callback(err)
callback(err);
});
},
function(callback) {
@ -136,7 +136,7 @@ describe('User CRUD tests', function() {
if(err){
callback(err);
}
callback(null, user.resetPasswordToken)
callback(null, user.resetPasswordToken);
});
},
function(resetPasswordToken, callback) {

View file

@ -78,13 +78,15 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
//Socket.io Client Dependency
script(src='/static/lib/socket.io-client/dist/socket.io.min.js')
script(src='/static/lib/jquery-ui/jquery-ui.js', type='text/javascript')
//Minified Bower Dependencies
script(src='/static/lib/angular/angular.min.js')
script(src='/static/dist/vendor.min.js')
script(src='/static/lib/angular-ui-date/src/date.js', type='text/javascript')
script(src='/static/lib/jquery-ui/jquery-ui.js', type='text/javascript')
script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js', integrity='sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa', crossorigin='anonymous')
//Application JavaScript Files
each jsFile in formJSFiles
script(type='text/javascript', src=jsFile)

View file

@ -3,7 +3,9 @@ extends layout.server.view.pug
block content
section.content(ui-view='', ng-cloak='')
script(src='/static/lib/file-saver.js/FileSaver.js', type='text/javascript')
link(rel='stylesheet', href='/static/lib/jquery-ui/themes/flick/jquery-ui.min.css')
script(src='/static/lib/jquery/jquery.min.js')
//Embedding The User Object
script(type='text/javascript').

View file

@ -31,8 +31,8 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
// Fav Icon
link(href='/static/modules/core/img/brand/favicon.ico', rel='shortcut icon', type='image/x-icon')
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css')
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u', crossorigin='anonymous')
link(rel='stylesheet', href='/static/lib/font-awesome/css/font-awesome.min.css')
link(rel='stylesheet', href='/static/lib/bootstrap/dist/css/bootstrap.min.css')
link(rel='stylesheet', href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,900')
//Bower CSS dependencies
@ -40,7 +40,6 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
link(rel='stylesheet', href=bowerCssFile)
link(rel='stylesheet', href='/static/lib/angular-input-stars/angular-input-stars.css')
link(rel='stylesheet', href='/static/lib/jquery-ui/themes/flick/jquery-ui.css')
link(rel='stylesheet', href='/static/modules/core/css/github-fork-ribbon.css')
// end Bower CSS dependencies
//Application CSS Files

View file

@ -25,7 +25,6 @@
"angular-permission": "~1.1.1",
"file-saver.js": "~1.20150507.2",
"angular-bootstrap-colorpicker": "~3.0.19",
"angular-ui-router-tabs": "~1.7.0",
"angular-scroll": "^1.0.0",
"angular-sanitize": "1.4.14",
"v-button": "^1.1.1",
@ -33,7 +32,6 @@
"raven-js": "^3.0.4",
"tableExport.jquery.plugin": "^1.5.1",
"js-yaml": "^3.6.1",
"angular-ui-select": "https://github.com/tellform/ui-select.git#compiled",
"angular-translate": "~2.11.0",
"ng-translate": "*",
"deep-diff": "^0.3.4",
@ -42,17 +40,22 @@
"mobile-detect": "^1.3.3",
"socket.io-client": "^1.7.2",
"css-toggle-switch": "^4.0.2",
"angular-strap": "^2.3.12"
"angular-strap": "^2.3.12",
"textAngular": "^1.5.16",
"angular-ui-select": "^0.19.8",
"angular-bootstrap-switch": "^0.5.2",
"jquery": "^3.2.1"
},
"resolutions": {
"angular-bootstrap": "^0.14.0",
"angular": "1.4.14",
"angular-ui-select": "compiled",
"jspdf": "~1.0.178",
"angular-sanitize": "1.4.14",
"angular-ui-sortable": "^0.17.1",
"angular-ui-date": "~0.0.11",
"angular-input-stars-directive": "master"
"angular-input-stars-directive": "master",
"angular-ui-select": "^0.19.8",
"jquery": "^3.2.1"
},
"overrides": {
"BOWER-PACKAGE": {

View file

@ -39,8 +39,9 @@ var configureSocketIO = function (app, db) {
var supportedLanguages = ['en', 'de', 'fr', 'it', 'es'];
function containsAnySupportedLanguages(preferredLanguages){
for (var i = 0; i < preferredLanguages.length; i++) {
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
var i, currIndex;
for (i = 0; i < preferredLanguages.length; i++) {
currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
if (currIndex > -1) {
return supportedLanguages[currIndex];
}
@ -226,7 +227,6 @@ module.exports = function(db) {
// Setting the app router and static folder
app.use('/static', express.static(path.resolve('./public')));
app.use('/uploads', express.static(path.resolve('./uploads')));
// CookieParser should be above session
app.use(cookieParser());
@ -261,6 +261,7 @@ module.exports = function(db) {
//Visitor Language Detection
app.use(function(req, res, next) {
var acceptLanguage = req.headers['accept-language'];
var languages, supportedLanguage;
if(acceptLanguage){
@ -270,13 +271,12 @@ module.exports = function(db) {
if(!req.user && supportedLanguage !== null){
var currLanguage = res.cookie('userLang');
if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){
res.clearCookie('userLang');
res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies.userLang !== req.user.language) ){
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
}
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies['userLang'] !== req.user.language) ){
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
}
next();
});

BIN
dump.rdb Normal file

Binary file not shown.

View file

@ -1,4 +1,4 @@
'use strict';
var bowerArray = ['public/lib/angular/angular.min.js',
'public/lib/angular-scroll/angular-scroll.min.js',

579
package-lock.json generated
View file

@ -4412,6 +4412,15 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "0.1.1"
}
},
"extendr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/extendr/-/extendr-2.1.0.tgz",
@ -6283,6 +6292,301 @@
}
}
},
"grunt-lcov-merge": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/grunt-lcov-merge/-/grunt-lcov-merge-1.2.3.tgz",
"integrity": "sha1-hGb401OfCItGSaYP/F7dLeklVBE=",
"dev": true,
"requires": {
"async": "2.5.0",
"chalk": "1.1.3",
"lcov-result-merger": "1.2.0",
"through2": "0.6.5",
"vinyl": "1.2.0",
"vinyl-fs": "2.4.4"
},
"dependencies": {
"async": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
"integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
"dev": true,
"requires": {
"lodash": "4.17.4"
}
},
"clone": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
"dev": true
},
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
"dev": true,
"requires": {
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"dev": true,
"requires": {
"is-glob": "3.1.0",
"path-dirname": "1.0.2"
}
},
"glob-stream": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz",
"integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=",
"dev": true,
"requires": {
"extend": "3.0.1",
"glob": "5.0.15",
"glob-parent": "3.1.0",
"micromatch": "2.3.11",
"ordered-read-streams": "0.3.0",
"through2": "0.6.5",
"to-absolute-glob": "0.1.1",
"unique-stream": "2.2.1"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"requires": {
"is-extglob": "2.1.1"
}
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"merge-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
"integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
"dev": true,
"requires": {
"readable-stream": "2.3.3"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
}
},
"ordered-read-streams": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz",
"integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=",
"dev": true,
"requires": {
"is-stream": "1.1.0",
"readable-stream": "2.3.3"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "0.0.1",
"string_decoder": "0.10.31"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
},
"through2": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
"dev": true,
"requires": {
"readable-stream": "1.0.34",
"xtend": "4.0.1"
}
},
"vinyl": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz",
"integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=",
"dev": true,
"requires": {
"clone": "1.0.2",
"clone-stats": "0.0.1",
"replace-ext": "0.0.1"
}
},
"vinyl-fs": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz",
"integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=",
"dev": true,
"requires": {
"duplexify": "3.5.1",
"glob-stream": "5.3.5",
"graceful-fs": "4.1.11",
"gulp-sourcemaps": "1.6.0",
"is-valid-glob": "0.3.0",
"lazystream": "1.0.0",
"lodash.isequal": "4.5.0",
"merge-stream": "1.0.1",
"mkdirp": "0.5.1",
"object-assign": "4.1.1",
"readable-stream": "2.3.3",
"strip-bom": "2.0.0",
"strip-bom-stream": "1.0.0",
"through2": "2.0.3",
"through2-filter": "2.0.0",
"vali-date": "1.0.0",
"vinyl": "1.2.0"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"through2": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
"integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
"dev": true,
"requires": {
"readable-stream": "2.3.3",
"xtend": "4.0.1"
}
}
}
}
}
},
"grunt-legacy-log": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz",
@ -6451,6 +6755,44 @@
"wiredep": "4.0.0"
}
},
"gulp-sourcemaps": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz",
"integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=",
"dev": true,
"requires": {
"convert-source-map": "1.1.3",
"graceful-fs": "4.1.11",
"strip-bom": "2.0.0",
"through2": "2.0.3",
"vinyl": "1.2.0"
},
"dependencies": {
"clone": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
"dev": true
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
},
"vinyl": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz",
"integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=",
"dev": true,
"requires": {
"clone": "1.0.2",
"clone-stats": "0.0.1",
"replace-ext": "0.0.1"
}
}
}
},
"gzip-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz",
@ -7299,6 +7641,12 @@
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"is-valid-glob": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz",
"integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=",
"dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -7947,6 +8295,15 @@
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
},
"lazystream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
"integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
"dev": true,
"requires": {
"readable-stream": "2.3.3"
}
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
@ -7962,6 +8319,185 @@
"integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=",
"dev": true
},
"lcov-result-merger": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-1.2.0.tgz",
"integrity": "sha1-XeHmQm+IWSm3c1fwFN5f7h2tBVM=",
"dev": true,
"requires": {
"through2": "2.0.3",
"vinyl": "1.2.0",
"vinyl-fs": "2.4.4"
},
"dependencies": {
"clone": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
"dev": true
},
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
"dev": true,
"requires": {
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"dev": true,
"requires": {
"is-glob": "3.1.0",
"path-dirname": "1.0.2"
}
},
"glob-stream": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz",
"integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=",
"dev": true,
"requires": {
"extend": "3.0.1",
"glob": "5.0.15",
"glob-parent": "3.1.0",
"micromatch": "2.3.11",
"ordered-read-streams": "0.3.0",
"through2": "0.6.5",
"to-absolute-glob": "0.1.1",
"unique-stream": "2.2.1"
},
"dependencies": {
"readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "0.0.1",
"string_decoder": "0.10.31"
}
},
"through2": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
"dev": true,
"requires": {
"readable-stream": "1.0.34",
"xtend": "4.0.1"
}
}
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"requires": {
"is-extglob": "2.1.1"
}
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"merge-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
"integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
"dev": true,
"requires": {
"readable-stream": "2.3.3"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.8"
}
},
"ordered-read-streams": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz",
"integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=",
"dev": true,
"requires": {
"is-stream": "1.1.0",
"readable-stream": "2.3.3"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
},
"vinyl": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz",
"integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=",
"dev": true,
"requires": {
"clone": "1.0.2",
"clone-stats": "0.0.1",
"replace-ext": "0.0.1"
}
},
"vinyl-fs": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz",
"integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=",
"dev": true,
"requires": {
"duplexify": "3.5.1",
"glob-stream": "5.3.5",
"graceful-fs": "4.1.11",
"gulp-sourcemaps": "1.6.0",
"is-valid-glob": "0.3.0",
"lazystream": "1.0.0",
"lodash.isequal": "4.5.0",
"merge-stream": "1.0.1",
"mkdirp": "0.5.1",
"object-assign": "4.1.1",
"readable-stream": "2.3.3",
"strip-bom": "2.0.0",
"strip-bom-stream": "1.0.0",
"through2": "2.0.3",
"through2-filter": "2.0.0",
"vali-date": "1.0.0",
"vinyl": "1.2.0"
}
}
}
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@ -8174,6 +8710,12 @@
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@ -9641,6 +10183,12 @@
"no-case": "2.3.2"
}
},
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
@ -10694,6 +11242,12 @@
"is-finite": "1.0.2"
}
},
"replace-ext": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz",
"integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=",
"dev": true
},
"request": {
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
@ -11441,6 +11995,16 @@
"is-utf8": "0.2.1"
}
},
"strip-bom-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz",
"integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=",
"dev": true,
"requires": {
"first-chunk-stream": "1.0.0",
"strip-bom": "2.0.0"
}
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
@ -11684,6 +12248,15 @@
"os-tmpdir": "1.0.2"
}
},
"to-absolute-glob": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz",
"integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=",
"dev": true,
"requires": {
"extend-shallow": "2.0.1"
}
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@ -12020,6 +12593,12 @@
}
}
},
"vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
"integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",

View file

@ -41,7 +41,7 @@
"express": "~4.13.3",
"express-session": "~1.12.1",
"glob": "^7.0.3",
"grunt": "~0.4.1",
"grunt": "^0.4.5",
"grunt-concurrent": "~2.3.0",
"grunt-contrib-csslint": "~1.0.0",
"grunt-contrib-cssmin": "~1.0.1",

View file

@ -4,7 +4,7 @@
var ApplicationConfiguration = (function() {
// Init module configuration options
var applicationModuleName = 'TellForm';
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'ngSanitize', 'vButton', 'ngResource', 'TellForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate', 'view-form'];
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'ngSanitize', 'vButton', 'ngResource', 'TellForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate'];
// Add a new vertical module
var registerModule = function(moduleName, dependencies) {

View file

@ -4236,18 +4236,16 @@ angular.module('view-form').directive('keyToOption', function(){
'use strict';
angular.module('view-form').directive('keyToTruthy', ["$rootScope", function($rootScope){
return {
restrict: 'A',
scope: {
field: '=',
nextField: '&'
},
link: function($scope, $element, $attrs) {
$element.bind('keydown keypress', function(event) {
var keyCode = event.which || event.keyCode;
var truthyKeyCode = $attrs.keyCharTruthy.charCodeAt(0) - 32;
var falseyKeyCode = $attrs.keyCharFalsey.charCodeAt(0) - 32;
// Init the application configuration module for AngularJS application
var ApplicationConfiguration = (function() {
// Init module configuration options
var applicationModuleName = 'TellForm';
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'ngSanitize', 'vButton', 'ngResource', 'TellForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate'];
// Add a new vertical module
var registerModule = function(moduleName, dependencies) {
// Create angular module
angular.module(moduleName, dependencies || []);
if(keyCode === truthyKeyCode ) {
event.preventDefault();
@ -5102,8 +5100,6 @@ angular.module('view-form').directive('submitFormDirective', ["$http", "TimeCoun
};
$rootScope.prevField = $scope.prevField = function(){
console.log('prevField');
console.log($scope.selected);
var selected_index = $scope.selected.index - 1;
if($scope.selected.index > 0){
$scope.setActiveField(null, selected_index, true);
@ -5197,17 +5193,6 @@ angular.module('view-form').directive('submitFormDirective', ["$http", "TimeCoun
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
}
//Get rid of unnessecary attributes for each form field
delete form.form_fields[i].submissionId;
delete form.form_fields[i].disabled;
delete form.form_fields[i].ratingOptions;
delete form.form_fields[i].fieldOptions;
delete form.form_fields[i].logicJump;
delete form.form_fields[i].description;
delete form.form_fields[i].validFieldTypes;
delete form.form_fields[i].fieldType;
}
setTimeout(function () {

4
public/dist/application.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -372,7 +372,6 @@ div.config-form .row.field {
background-color:#efefef;
}
/*Styles for add fields tab*/
.admin-form .add-field {
background-color: #ddd;
@ -389,10 +388,6 @@ div.config-form .row.field {
border-radius: 4px;
}
.admin-form .oscar-field-select {
margin: 10px 0 10px;
}
.view-form-btn.span {
padding-right:0.6em;
}

View file

@ -285,8 +285,6 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
};
$rootScope.prevField = $scope.prevField = function(){
console.log('prevField');
console.log($scope.selected);
var selected_index = $scope.selected.index - 1;
if($scope.selected.index > 0){
$scope.setActiveField(null, selected_index, true);
@ -380,17 +378,6 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
}
//Get rid of unnessecary attributes for each form field
delete form.form_fields[i].submissionId;
delete form.form_fields[i].disabled;
delete form.form_fields[i].ratingOptions;
delete form.form_fields[i].fieldOptions;
delete form.form_fields[i].logicJump;
delete form.form_fields[i].description;
delete form.form_fields[i].validFieldTypes;
delete form.form_fields[i].fieldType;
}
setTimeout(function () {

View file

@ -15,11 +15,10 @@
</div>
<div class="col-xs-12 field-input">
<ui-select ng-model="field.fieldValue"
class="dropdown"
ng-focus="setActiveField(field._id, null, false)"
theme="selectize"
search-enabled="true"
search-by="option_value"
set-search-to-answer="true"
ng-required="field.required"
on-tab-and-shift-key="prevField()"
on-tab-key="nextField()"

View file

@ -21,8 +21,13 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
params: fromParams
}
<<<<<<< HEAD
var statesToIgnore = ['home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
=======
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
>>>>>>> 2.20
//Redirect to listForms if user is authenticated
if(statesToIgnore.indexOf(toState.name) > 0){
if(Auth.isAuthenticated()){

View file

@ -6,16 +6,30 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
//Configure Form Tab View
ADVANCED_SETTINGS: 'Advanced Settings',
FORM_NAME: 'Form Name',
FORM_STATUS: 'Form Status',
FORM_NAME: 'Your tellform is called',
FORM_STATUS: 'Status',
PUBLIC: 'Public',
PRIVATE: 'Private',
GA_TRACKING_CODE: 'Google Analytics Tracking Code',
DISPLAY_FOOTER: 'Display Form Footer?',
DISPLAY_FOOTER: 'Form Footer',
SAVE_CHANGES: 'Save Changes',
CANCEL: 'Cancel',
DISPLAY_START_PAGE: 'Display Start Page?',
DISPLAY_END_PAGE: 'Display Custom End Page?',
DISPLAY_START_PAGE: 'Start Page',
DISPLAY_END_PAGE: 'Custom End Page',
GENERAL_TAB: 'General',
SELF_NOTIFICATIONS_TAB: 'Self notifications',
RESPONDENT_NOTIFICATIONS_TAB: 'Respondent notifications',
SEND_NOTIFICATION_TO: 'Send to',
NO_EMAIL_FIELD_WARNING: 'Error: You need an email field in your form to send the email to your form respondent',
REPLY_TO: 'Reply to',
EMAIL_SUBJECT: 'Subject',
EMAIL_MESSAGE: 'Message',
ENABLE_RESPONDENT_NOTIFICATIONS: 'Respondent Notifications are currently',
ENABLE_SELF_NOTIFICATIONS: 'Self Notifications are currently',
TOGGLE_ENABLED: 'Enabled',
TOGGLE_DISABLED: 'Disabled',
ADD_VARIABLE_BUTTON: 'Add variable',
//List Forms View
CREATE_A_NEW_FORM: 'Create a new form',
@ -47,7 +61,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
DELETE_FORM_MD: 'Delete Form',
DELETE: 'Delete',
FORM: 'Form',
VIEW: 'View',
VIEW_MY_TELLFORM: 'View my tellform',
LIVE: 'Live',
PREVIEW: 'Preview',
COPY: 'Copy',

View file

@ -5,16 +5,31 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('fr', {
// Configurer la vue de l'onglet Formulaire
ADVANCED_SETTINGS: 'Paramètres avancés',
FORM_NAME: "Nom du formulaire",
FORM_STATUS: 'Statut du formulaire',
FORM_NAME: "Votre tellform est appelé",
FORM_STATUS: 'Statut',
PUBLIC: 'Public',
PRIVATE: "Privé",
GA_TRACKING_CODE: "Code de suivi Google Analytics",
DISPLAY_FOOTER: "Afficher le pied de formulaire?",
DISPLAY_FOOTER: "Pied de formulaire",
SAVE_CHANGES: 'Enregistrer les modifications',
CANCEL: 'Annuler',
DISPLAY_START_PAGE: "Afficher la page de démarrage?",
DISPLAY_END_PAGE: "Afficher la page de fin personnalisée?",
DISPLAY_START_PAGE: "Page de démarrage",
DISPLAY_END_PAGE: "Page de fin personnalisée",
GENERAL_TAB: 'General',
SELF_NOTIFICATIONS_TAB: 'Self notifications',
RESPONDANT_NOTIFICATIONS_TAB: 'Respondent notifications',
SEND_NOTIFICATION_TO: 'Envoyer à',
NO_EMAIL_FIELD_WARNING: 'Erreur: Vous avez besoin d\'un champ e-mail dans votre formulaire pour envoyer l\'e-mail au répondant de votre formulaire',
REPLY_TO: "Répondre à",
EMAIL_SUBJECT: 'Sujet',
EMAIL_MESSAGE: "Message",
ENABLE_RESPONDENT_NOTIFICATIONS: 'Les notifications des répondants sont actuellement',
ENABLE_SELF_NOTIFICATIONS: 'Les notifications automatiques sont actuellement',
TOGGLE_ENABLED: 'Activé',
TOGGLE_DISABLED: 'Désactivé',
ADD_VARIABLE_BUTTON: "Ajouter une variable",
// Afficher les formulaires
CREATE_A_NEW_FORM: "Créer un nouveau formulaire",
@ -46,7 +61,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
DELETE_FORM_MD: "Supprimer le formulaire",
DELETE: "Supprimer",
FORM: 'Formulaire',
VIEW: "Afficher",
VIEW_MY_TELLFORM: "Afficher ma forme",
LIVE: "Live",
PREVIEW: 'Aperçu',
COPY: "Copier",

View file

@ -5,16 +5,31 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('de', {
// Konfigurieren der Formularregisterkarte
ADVANCED_SETTINGS: 'Erweiterte Einstellungen',
FORM_NAME: 'Formularname',
FORM_STATUS: 'Formularstatus',
FORM_NAME: 'Ihr tellform heißt',
FORM_STATUS: 'Status',
PUBLIC: 'Öffentlich',
PRIVATE: 'Privat',
GA_TRACKING_CODE: 'Google Analytics Tracking-Code',
DISPLAY_FOOTER: 'Formularfußzeile anzeigen?',
DISPLAY_FOOTER: 'Fußzeile',
SAVE_CHANGES: 'Änderungen speichern',
CANCEL: 'Abbrechen',
DISPLAY_START_PAGE: 'Startseite anzeigen?',
DISPLAY_END_PAGE: 'Benutzerdefinierte Endseite anzeigen?',
DISPLAY_START_PAGE: 'Startseite',
DISPLAY_END_PAGE: 'Benutzerdefinierte Endseite',
GENERAL_TAB: 'Allgemein',
SELF_NOTIFICATIONS_TAB: 'Selbstbenachrichtigungen',
RESPONDANT_NOTIFICATIONS_TAB: 'Beantwortungsbenachrichtigungen',
SEND_NOTIFICATION_TO: 'Senden an',
NO_EMAIL_FIELD_WARNING: 'Fehler: Sie benötigen ein E-Mail-Feld in Ihrem Formular, um die E-Mail an Ihr Formular zu senden.',
REPLY_TO: 'Antworten auf',
EMAIL_SUBJECT: "Betreff",
EMAIL_MESSAGE: 'Nachricht',
ENABLE_RESPONDENT_NOTIFICATIONS: 'Antwortbenachrichtigungen sind derzeit',
ENABLE_SELF_NOTIFICATIONS: 'Selbstbenachrichtigungen sind derzeit',
TOGGLE_ENABLED: 'Aktiviert',
TOGGLE_DISABLED: 'Deaktiviert',
ADD_VARIABLE_BUTTON: 'Variable hinzufügen',
// Listenformularansicht
CREATE_A_NEW_FORM: 'Erstelle ein neues Formular',
@ -46,7 +61,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
DELETE_FORM_MD: 'Formular löschen',
DELETE: 'Löschen',
FORM: 'Formular',
VIEW: 'Ansicht',
VIEW_MY_TELLFORM: 'Mein tellform anzeigen',
LIVE: 'Leben',
PREVIEW: 'Vorschau',
COPY: 'Kopieren',

View file

@ -5,16 +5,31 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('it', {
// Configura la visualizzazione scheda modulo
ADVANCED_SETTINGS: 'Impostazioni avanzate',
FORM_NAME: 'Nome modulo',
FORM_STATUS: 'Stato modulo',
FORM_NAME: 'Il tuo tellform è chiamato',
FORM_STATUS: 'Stato',
PUBLIC: 'pubblico',
PRIVATE: 'Privato',
GA_TRACKING_CODE: 'Codice di monitoraggio di Google Analytics',
DISPLAY_FOOTER: 'Visualizza piè di pagina?',
DISPLAY_FOOTER: 'Piè di pagina',
SAVE_CHANGES: 'Salva modifiche',
CANCEL: 'Annulla',
DISPLAY_START_PAGE: 'Visualizza pagina iniziale?',
DISPLAY_END_PAGE: 'Mostra pagina finale personalizzata?',
DISPLAY_START_PAGE: 'Pagina iniziale',
DISPLAY_END_PAGE: 'Pagina finale personalizzata',
GENERAL_TAB: 'Generale',
SELF_NOTIFICATIONS_TAB: 'Autodiagnosi',
RESPONDANT_NOTIFICATIONS_TAB: 'Notifiche rispondenti',
SEND_NOTIFICATION_TO: 'Invia a',
NO_EMAIL_FIELD_WARNING: 'Errore: Hai bisogno di un campo e-mail nel tuo modulo per inviare l\'email al tuo interlocutore',
REPLY_TO: 'Rispondi a',
EMAIL_SUBJECT: 'Oggetto',
EMAIL_MESSAGE: 'Messaggio',
ENABLE_RESPONDENT_NOTIFICATIONS: 'Notifiche rispondenti sono attualmente',
ENABLE_SELF_NOTIFICATIONS: 'Le notifiche auto sono attualmente',
TOGGLE_ENABLED: 'Abilitato',
TOGGLE_DISABLED: 'disabilitato',
ADD_VARIABLE_BUTTON: 'Aggiungi variabile',
// Visualizzazione dei moduli di elenco
CREATE_A_NEW_FORM: 'Crea un nuovo modulo',
@ -46,7 +61,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
DELETE_FORM_MD: 'Elimina modulo',
DELETE: 'Elimina',
FORM: 'Forma',
VIEW: 'Visualizza',
VIEW_MY_TELLFORM: 'Visualizza la mia informazione',
LIVE: 'Live',
PREVIEW: 'Anteprima',
COPY: 'Copia',

View file

@ -3,19 +3,33 @@
angular.module('forms').config(['$translateProvider', function ($translateProvider) {
$translateProvider.translations('es', {
//Configure Form Tab View
ADVANCED_SETTINGS: 'Configuraciones avanzadas',
FORM_NAME: 'Nombre del formulario',
FORM_STATUS: 'Estado del formulario',
FORM_NAME: 'Tu tellform se llama',
FORM_STATUS: 'Estado',
PUBLIC: 'Público',
PRIVATE: 'Privado',
GA_TRACKING_CODE: 'Código de Google Analytics',
DISPLAY_FOOTER: '¿Mostrar pie de página?',
DISPLAY_FOOTER: 'Pie de página',
SAVE_CHANGES: 'Grabar',
CANCEL: 'Cancelar',
DISPLAY_START_PAGE: '¿Mostrar página de inicio?',
DISPLAY_END_PAGE: '¿Mostrar paǵina de fin?',
DISPLAY_START_PAGE: 'Página de inicio',
DISPLAY_END_PAGE: 'Página final personalizada',
SELF_NOTIFICATIONS_TAB: 'Auto notificaciones',
RESPONDANT_NOTIFICATIONS_TAB: 'Notificaciones de los demandados',
GENERAL_TAB: 'Général',
SEND_NOTIFICATION_TO: 'Enviar a',
NO_EMAIL_FIELD_WARNING: 'Error: necesita un campo de correo electrónico en su formulario para enviar el correo electrónico a su encuestado',
REPLY_TO: 'Responder a',
EMAIL_SUBJECT: 'Asunto',
EMAIL_MESSAGE: 'Mensaje',
ENABLE_RESPONDENT_NOTIFICATIONS: 'Las notificaciones de los demandados son actualmente',
ENABLE_SELF_NOTIFICATIONS: 'Las notificaciones automáticas están actualmente',
TOGGLE_ENABLED: 'Habilitado',
TOGGLE_DISABLED: 'Desactivado',
ADD_VARIABLE_BUTTON: 'Agregar variable',
//List Forms View
CREATE_A_NEW_FORM: 'Crear formulario',
@ -47,7 +61,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
DELETE_FORM_MD: 'Borrar formulario',
DELETE: 'Borrar',
FORM: 'Formulario',
VIEW: 'Vista',
VIEW_MY_TELLFORM: 'Ver mi tellform',
LIVE: 'Online',
PREVIEW: 'Vista previa',
COPY: 'Copiar',

View file

@ -4,19 +4,12 @@
angular.module('forms').controller('AdminFormController', ['$rootScope', '$window', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter', '$translate',
function($rootScope, $window, $scope, $stateParams, $state, Forms, CurrentForm, $http, $uibModal, myForm, $filter, $translate) {
//Set active tab to Create
$scope.activePill = 0;
$scope.copied = false;
$scope.onCopySuccess = function (e) {
$scope.copied = true;
};
$scope = $rootScope;
$scope.animationsEnabled = true;
$scope.myform = myForm;
$rootScope.saveInProgress = false;
$scope.oldForm = _.cloneDeep($scope.myform);
$scope.designTabActive = false
CurrentForm.setForm($scope.myform);
@ -36,7 +29,6 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
$scope.actualFormURL = window.location.protocol + '//' + window.location.host + $scope.formURL;
}
var refreshFrame = $scope.refreshFrame = function(){
if(document.getElementById('iframe')) {
document.getElementById('iframe').contentWindow.location.reload();
@ -44,13 +36,59 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
};
$scope.tabData = [
{
heading: $filter('translate')('CREATE_TAB'),
route: 'viewForm.create',
active: false
},
{
heading: $filter('translate')('CONFIGURE_TAB'),
templateName: 'configure'
route: 'viewForm.configure.general',
active: false
},
{
heading: $filter('translate')('ANALYZE_TAB'),
route: 'viewForm.analyze',
active: false
},
{
heading: $filter('translate')('SHARE_TAB'),
route: 'viewForm.share',
active: false
},
{
heading: $filter('translate')('DESIGN_TAB'),
route: 'viewForm.design',
active: false
}
];
$scope.designTabActive = false
$scope.go = function(tab){
var currParentState = $state.current.name.split('.').slice(0,2).join('.');
var tabParentState = tab.route.split('.').slice(0,2).join('.');
if(currParentState !== tabParentState && tabParentState !== 'viewForm.configure.general'){
$state.go(tab.route);
}
};
function setActiveTab() {
$scope.tabData.forEach(function(tab) {
var currentTabState = $state.current.name.split('.').slice(0,2).join('.');
var tabRouteState = tab.route.split('.').slice(0,2).join('.');
tab.active = (currentTabState === tabRouteState);
if(tab.active && tab.route === 'viewForm.design'){
$scope.designTabActive = true;
} else {
$scope.designTabActive = false;
}
});
}
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
$scope.deactivateDesignTab = function(){
$scope.designTabActive = false

View file

@ -0,0 +1,43 @@
.tag {
background-color: #999;
border-radius: 3px 3px 3px 3px;
border: 0;
color: #FFFFFF;
font-style: inherit;
font-size: 11px;
padding: 4px 5px;
margin: 0 2px 2px 2px;
font-family: inherit;
white-space: nowrap;
vertical-align: middle;
cursor: pointer !important;
pointer-events: none;
}
.email-subject.ta-root .ta-editor.ta-html, .email-subject .ta-scroll-window.form-control {
min-height: 0;
overflow: hidden;
height: auto;
border-radius: 4px;
box-shadow: none;
font-size: 18px;
padding-top: 10px;
}
.email-subject.ta-root .ta-scroll-window > .ta-bind {
min-height: 0;
outline: 0;
}
.ui-select input.form-control {
height: 34px;
padding: 6px;
}
.config-form .btn-secondary {
border-color: #DDDDDD;
}
.notification-toggle.toggle-switch {
margin: 5px 0;
}

View file

@ -247,9 +247,25 @@ div.config-form .row.field {
margin: 10px 0 10px;
}
.view-form-btn {
border: none;
}
.view-form-btn.span {
padding-right:0.6em;
}
.notification-row {
display: inline-block;
padding: 0 5px;
}
.status-light {
font-size: 10px;
}
.notification-row .status-light {
padding-top: 15px;
}
.status-light.status-light-off {
color: #BE0000;
}

View file

@ -1,23 +1,79 @@
'use strict';
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$http', 'Upload', 'CurrentForm',
function ($rootScope, $http, Upload, CurrentForm) {
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$filter', '$state',
function ($rootScope, $filter, $state) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/configure-form.client.view.html',
restrict: 'E',
scope: {
myform:'=',
user:'=',
pdfFields:'@',
formFields:'@'
myform:'='
},
controller: function($scope){
$scope.log = '';
$rootScope.myform = $scope.myform;
$scope.languages = $rootScope.languages;
$scope.resetForm = $rootScope.resetForm;
$scope.update = $rootScope.update;
$scope.$evalAsync(function() {
angular.element('.tag')
});
$scope.languages = ['en', 'fr', 'es', 'it', 'de'];
$scope.langCodeToWord = {
'en': 'English',
'fr': 'Français',
'es': 'Español',
'it': 'Italiàno',
'de': 'Deutsch'
};
$scope.wordToLangCode = {
'English': 'en',
'Français': 'fr',
'Español': 'es',
'Italiàno': 'it',
'Deutsch': 'de'
};
$scope.configureTabs = [
{
heading: $filter('translate')('GENERAL_TAB'),
route: 'viewForm.configure.general',
active: false
},
{
heading: $filter('translate')('SELF_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.self_notifications',
active: false
},
{
heading: $filter('translate')('RESPONDENT_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.respondent_notifications',
active: false
}
];
$scope.emailFields = $scope.myform.form_fields.filter(function(field){
return field.fieldType === 'email';
});
$scope.formHasEmailField = ($scope.emailFields.length > 0);
$scope.go = function(tab){
tab.active = true;
$state.go(tab.route);
};
function setActiveTab() {
$scope.configureTabs.forEach(function(tab) {
tab.active = ($state.current.name === tab.route);
});
}
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
}
};
}

View file

@ -0,0 +1,14 @@
'use strict';
angular.module('forms').directive('designFormDirective', [
function () {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/design-form.client.view.html',
restrict: 'E',
scope: {
myform:'=',
formurl: '='
}
}
}
]);

View file

@ -1,4 +1,3 @@
'use strict';
angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormFields', '$uibModal',

View file

@ -0,0 +1,16 @@
'use strict';
angular.module('forms').directive('shareFormDirective', ['$rootScope',
function ($rootScope) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/share-form.client.view.html',
restrict: 'E',
scope: {
actualformurl:'='
},
controller: function($scope){
$scope.actualFormURL = $scope.actualformurl;
}
};
}
]);

View file

@ -1,11 +1,18 @@
'use strict';
//TODO: DAVID: URGENT: Make this a $resource that fetches valid field types from server
<<<<<<< HEAD
angular.module('forms').service('FormFields', [ '$rootScope', '$translate', '$window',
function($rootScope, $translate, $window) {
console.log($window.user);
$translate.use($window.user.language);
=======
angular.module('forms').service('FormFields', [ '$rootScope', '$translate', 'Auth',
function($rootScope, $translate, Auth) {
var language = Auth.ensureHasCurrentUser().language;
$translate.use(language);
>>>>>>> 2.20
this.types = [
{

View file

@ -42,158 +42,26 @@
<div class="col-xs-1 col-sm-2">
<small class="pull-right">
<a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}">
<span class="hidden-xs hidden-sm">
{{ 'VIEW' | translate }}
<span ng-show="myform.isLive">
{{ 'LIVE' | translate }}
</span>
<span ng-hide="myform.isLive">{{ 'PREVIEW' | translate }}</span>
<i class="fa fa-external-link"></i>
<span>
{{ 'VIEW_MY_TELLFORM' | translate }}
</span>
<i class="status-light status-light-on fa fa-dot-circle-o" ng-if="myform.isLive"></i>
<i class="status-light status-light-off fa fa-dot-circle-o" ng-if="!myform.isLive"></i>
<i class="status-light status-light-on fa fa-circle" ng-if="myform.isLive"></i>
<i class="status-light status-light-off fa fa-circle" ng-if="!myform.isLive"></i>
</a>
</small>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="{{ 'CREATE_TAB' | translate }}" select="deactivateDesignTab()">
<edit-form-directive myform="myform"></edit-form-directive>
</uib-tab>
<uib-tab ng-repeat="tab in tabData" index="{{$index+1}}" heading="{{tab.heading}}" select="deactivateDesignTab()">
<div class='row' data-ng-include="'/static/modules/forms/admin/views/adminTabs/'+tab.templateName+'.html'"></div>
</uib-tab>
<uib-tab index="2" heading="{{ 'ANALYZE_TAB' | translate }}" select="deactivateDesignTab()">
<edit-submissions-form-directive myform="myform" user="myform.admin"></edit-submissions-form-directive>
</uib-tab>
<uib-tab ng-if="tabData" heading="{{ 'SHARE_TAB' | translate }}" index="{{tabData.length}}" select="deactivateDesignTab()">
<div class="config-form">
<div class="row">
<div class="col-sm-12">
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="{{ 'SHARE_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'TELLFORM_URL' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyURL"> <input id="copyURL" ng-value="actualFormURL" class="form-control ng-pristine ng-untouched ng-valid"> </span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyURL">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
<uib-tab index="1" heading="{{ 'EMBED_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'COPY_AND_PASTE' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyEmbedded">
<textarea id="copyEmbedded" class="form-control ng-pristine ng-untouched ng-valid" style="min-height:200px; width:100%; background-color: #FFFFCC; color: #30313F;">
&lt;!-- {{ 'CHANGE_WIDTH_AND_HEIGHT' | translate }} --&gt;
<iframe id="iframe" src="{{actualFormURL}}" style="width:100%;height:500px;"></iframe>
<div style="font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;">{{ 'POWERED_BY' | translate }} <a href="https://www.tellform.com" style="color: #999" target="_blank">TellForm</a></div>
</textarea>
</span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyEmbedded">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
</div>
</div>
</uib-tab>
<uib-tab class="design-tab" ng-if="tabData && myform.form_fields.length" heading="{{ 'DESIGN_TAB' | translate }}" index="{{tabData.length}}+1"
select="activateDesignTab()">
<div class="config-form design container">
<div class="row">
<div class="col-sm-4 col-xs-12">
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.backgroundColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/" ng-style="{ 'background-color': myform.design.colors.answerColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.buttonColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonTextColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.buttonTextColor }"/>
</div>
</div>
</div>
<div class="col-sm-8 hidden-xs" ng-if="designTabActive">
<div class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
<iframe id="iframe" ng-if="!!formURL" ng-src="{{formURL | trustSrc}}" style="border: none; box-shadow: 0px 0px 10px 0px grey; overflow: hidden; height: 400px; width: 90%; position: absolute;"></iframe>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-signup btn-rounded" type="button" ng-click="updateDesign(false, myform, false, false)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
</div>
<div class="col-sm-1">
<button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>
</div>
</div>
</div>
<div >
<uib-tabset vertical="true" type="pills">
<uib-tab ng-repeat="tab in tabData" active="tab.active" select="go(tab)" heading="{{tab.heading}}">
</uib-tab>
</uib-tabset>
</div>
<div class="col-xs-10">
<div ui-view></div>
</div>
</div>
</section>

View file

@ -1,2 +1,4 @@
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.default.css'>
<configure-form-directive myform="myform" user="user">
</configure-form-directive>

View file

@ -0,0 +1,106 @@
<!-- Settings -->
<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<div class="row field">
<div class="field-title col-sm-12">
<h4>{{ 'FORM_NAME' | translate }}</h4>
</div>
<div class="col-sm-12">
<input class=""
type="text"
ng-model="myform.title"
value="{{myform.title}}"
style="width: 100%;"
ng-minlength="4"
ng-pattern="/^[a-zA-Z0-9 \-.]*$/">
</div>
</div>
<div class="row field">
<div class="col-sm-4 field-title"><h4>{{ 'LANGUAGE' | translate }}</h4></div>
<div class="col-sm-8 field-input">
<ui-select ng-model="myform.language" search-enabled="false" theme="selectize">
<ui-select-match>
{{ langCodeToWord[$select.selected] }}
</ui-select-match>
<ui-select-choices repeat="language in languages">
<span ng-bind-html="langCodeToWord[language] | highlight: $select.search">
</span>
</ui-select-choices>
</select>
<span class="required-error" ng-show="field.required && !field.fieldValue">* {{ 'REQUIRED_FIELD' | translate }}</span>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-8">
<h4>{{ 'FORM_STATUS' | translate }}</h4>
</div>
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox" name="my-checkbox"
bs-switch ng-model="myform.isLive"
switch-on-text="{{ 'PUBLIC' | translate }}"
switch-off-text="{{ 'PRIVATE' | translate }}"
switch-on-color="success"
switch-off-color="danger">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h4>{{ 'GA_TRACKING_CODE' | translate }}</h4>
</div>
<div class="col-sm-12">
<input class=""
type="text"
ng-model="myform.analytics.gaCode"
value="{{myform.analytics.gaCode}}"
style="width: 100%;"
ng-minlength="4"
placeholder="UA-XXXXX-Y"
ng-pattern="/\bUA-\d{4,10}-\d{1,4}\b/">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-8">
<h4>{{ 'DISPLAY_FOOTER' | translate }}</h4>
</div>
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox"
bs-switch ng-model="myform.hideFooter"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-8">
<h4>{{ 'DISPLAY_START_PAGE' | translate }}</h4>
</div>
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox"
bs-switch ng-model="myform.startPage.showStart"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-8">
<h4>{{ 'DISPLAY_END_PAGE' | translate }}</h4>
</div>
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox"
bs-switch ng-model="myform.endPage.showEnd"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,81 @@
<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<div class="row field">
<div class="notification-row">
<i class="status-light status-light-on fa fa-circle" ng-if="myform.respondentNotifications.enabled"></i>
<i class="status-light status-light-off fa fa-circle" ng-if="!myform.respondentNotifications.enabled"></i>
</div>
<div class="notification-row">
<h5>{{ 'ENABLE_RESPONDENT_NOTIFICATIONS' | translate }}</h5>
</div>
<div class="notification-row">
<input class="toggle-switch notification-toggle" type="checkbox" switch-size="small"
bs-switch ng-model="myform.respondentNotifications.enabled"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>
</div>
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}:</h5>
</div>
<div class="col-sm-12 field-input">
<ui-select ng-model="myform.respondentNotifications.toField" theme="selectize">
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}}
</ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search">
</span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="row field" ng-if="!formHasEmailField">
<strong>
{{ 'NO_EMAIL_FIELD_WARNING' | translate }}
</strong>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}:</h5>
</div>
<div class="col-sm-12 field-input">
<input class="form-control"
type="text"
ng-model="myform.respondentNotifications.replyEmail"
placeholder="noreply@tellform.com">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}:</h5>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.respondentNotifications.subject" ta-toolbar="[['insertField']]" ta-default-wrap="n" ta-unsafe-sanitizer="true">
</text-angular>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.respondentNotifications.htmlTemplate"
ta-toolbar="[['bold','italics', 'insertField']]"
ta-unsafe-sanitizer="true">
</text-angular>
<div ng-bind="myform.respondentNotifications.htmlTemplate"></div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,74 @@
<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<div class="row field">
<div class='notification-row'>
<i class="status-light status-light-on fa fa-circle" ng-if="myform.selfNotifications.enabled"></i>
<i class="status-light status-light-off fa fa-circle" ng-if="!myform.selfNotifications.enabled"></i>
</div>
<div class='notification-row'>
<h5>{{ 'ENABLE_SELF_NOTIFICATIONS' | translate }}</h5>
</div>
<div class='notification-row'>
<input class="toggle-switch notification-toggle" type="checkbox" switch-size="small"
bs-switch ng-model="myform.selfNotifications.enabled"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}</h5>
</div>
<div class="col-sm-12 field-input">
<input class="form-control"
type="text"
ng-model="myform.selfNotifications.toEmails"
placeholder="email@domain.com,email2@domain2.com,etc">
</div>
</div>
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}</h5>
</div>
<div class="col-sm-12 ui-select field-input">
<ui-select ng-model="myform.selfNotifications.fromField" theme="selectize">
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}}
</ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search">
</span>
</ui-select-choices>
</ui-select>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}</h5>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.selfNotifications.subject" ta-toolbar="[['insertField']]"
ta-unsafe-sanitizer="true" ta-default-wrap="n"></text-angular>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
</div>
<div class="col-sm-12 field-input">
<text-angular ng-model="myform.selfNotifications.htmlTemplate" ta-toolbar="[['bold','italics', 'insertField']]" ta-unsafe-sanitizer="true"></text-angular>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1 @@
<design-form-directive myform="myform" formurl="formURL"></design-form-directive>

View file

@ -0,0 +1 @@
<share-form-directive actualformurl="actualFormURL"></share-form-directive>

View file

@ -1,133 +1,13 @@
<div class="config-form container">
<!-- Settings -->
<div class="row">
<div class="col-sm-offset-2 col-sm-4">
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'FORM_NAME' | translate }}</h5>
</div>
<uib-tabset active="activePill" type="pills">
<uib-tab ng-repeat="tab in configureTabs" active="tab.active" select="go(tab)" heading="{{tab.heading}}">
<div ui-view></div>
</uib-tab>
</uib-tabset>
</div>
<div class="col-sm-12">
<input class="form-control"
type="text"
ng-model="myform.title"
value="{{myform.title}}"
style="width: 100%;"
ng-minlength="4"
ng-pattern="/^[a-zA-Z0-9 \-.]*$/">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'FORM_STATUS' | translate }}</h5>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.isLive" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'PUBLIC' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.isLive" ng-required="true" />
&nbsp;<span>{{ 'PRIVATE' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="col-sm-12 field-title">{{ 'LANGUAGE' | translate }}</div>
<div class="col-sm-12 field-input">
<select ng-model="myform.language">
<option ng-repeat="language in languages"
ng-selected="language == myform.language"
value="{{language}}">
{{language}}
</option>
</select>
<span class="required-error" ng-show="field.required && !field.fieldValue">* {{ 'REQUIRED_FIELD' | translate }}</span>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'GA_TRACKING_CODE' | translate }}</h5>
</div>
<div class="col-sm-12">
<input class="form-control"
type="text"
ng-model="myform.analytics.gaCode"
value="{{myform.analytics.gaCode}}"
style="width: 100%;"
ng-minlength="4"
placeholder="UA-XXXXX-Y"
ng-pattern="/\bUA-\d{4,10}-\d{1,4}\b/">
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'DISPLAY_FOOTER' | translate }}</h5>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'DISPLAY_START_PAGE' | translate }}</h5>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.startPage.showStart" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.startPage.showStart" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'DISPLAY_END_PAGE' | translate }}</h5>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.endPage.showEnd" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.endPage.showEnd" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-signup btn-rounded" type="button" ng-click="update(false, myform, false, false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>

View file

@ -0,0 +1,76 @@
<div class="config-form design container">
<div class="row">
<div class="col-sm-4 col-xs-12">
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.backgroundColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/" ng-style="{ 'background-color': myform.design.colors.answerColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.buttonColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonTextColor"
ng-pattern="/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/"
ng-style="{ 'background-color': myform.design.colors.buttonTextColor }"/>
</div>
</div>
</div>
<div class="col-sm-8 hidden-xs">
<div class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
<iframe id="iframe" ng-if="!!formurl" ng-src="{{formurl | trustSrc}}" style="border: none; box-shadow: 0px 0px 10px 0px grey; overflow: hidden; height: 400px; width: 90%; position: absolute;"></iframe>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-signup btn-rounded" type="button" ng-click="updateDesign(false, myform, false, false)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
</div>
<div class="col-sm-1">
<button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
<div class="submissions-table container">
<div class="submissions-table row">
<div class="row text-center analytics">
<div class="col-xs-12 header-title">
<div class="col-xs-3">

View file

@ -0,0 +1,44 @@
<div class="config-form row">
<div class="row">
<div class="col-sm-12">
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="{{ 'SHARE_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'TELLFORM_URL' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyURL"> <input id="copyURL" ng-value="actualFormURL" class="form-control ng-pristine ng-untouched ng-valid"> </span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyURL">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
<uib-tab index="1" heading="{{ 'EMBED_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'COPY_AND_PASTE' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyEmbedded">
<textarea id="copyEmbedded" class="form-control ng-pristine ng-untouched ng-valid" style="min-height:200px; width:100%; background-color: #FFFFCC; color: #30313F;">
&lt;!-- {{ 'CHANGE_WIDTH_AND_HEIGHT' | translate }} --&gt;
<iframe id="iframe" src="{{actualFormURL}}" style="width:100%;height:500px;"></iframe>
<div style="font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;">{{ 'POWERED_BY' | translate }} <a href="https://www.tellform.com" style="color: #999" target="_blank">TellForm</a></div>
</textarea>
</span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyEmbedded">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
</div>
</div>

View file

@ -224,13 +224,13 @@ form .row.field {
font-size:0.8em;
}
form .row.field.dropdown > .field-input input {
form .dropdown > .field-input input {
min-height: 34px;
border-width: 0 0 2px 0;
border-radius: 5px;
}
form .row.field.dropdown > .field-input input:focus {
form .dropdown > .field-input input:focus {
border: none;
}

View file

@ -6,6 +6,15 @@ angular.module('forms').run(['Menus',
// Set top bar menu items
Menus.addMenuItem('topbar', 'My Forms', 'forms', '', '/forms', false);
}
]).run(['$rootScope', '$state',
function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
}
});
}
]).filter('secondsToDateTime', [function() {
return function(seconds) {
return new Date(1970, 0, 1).setSeconds(seconds);
@ -46,4 +55,31 @@ angular.module('forms').run(['Menus',
directive.replace = true;
return $delegate;
});
}]);
}]).config(['$provide', function ($provide){
$provide.decorator('taOptions', ['$delegate', 'taRegisterTool', '$translate', '$window', function(taOptions, taRegisterTool, $translate, $window) {
taRegisterTool('insertField', {
display: '<div class="dropdown" uib-dropdown is-open="isopen">\
<div class="dropdown-toggle" ng-disabled="isDisabled()" uib-dropdown-toggle>\
<span>{{ "ADD_VARIABLE_BUTTON" | translate }}</span>\
<b class="caret"></b>\
</div>\
<ul class="dropdown-menu">\
<li ng-repeat="field in $root.myform.form_fields" ng-click="onClickField(field.globalId, field.title)">\
{{field.title}}\
</li>\
</ul>\
</div>',
onClickField: function(field_id, field_name){
this.$editor().wrapSelection('insertHTML', '<var class="tag" contenteditable="false" id="field:' + field_id + '">' + field_name + '</var>', false);
},
action: function(){
}
});
taOptions.defaultTagAttributes['var'] = {
'contenteditable': 'false'
};
return taOptions;
}]);
}]);

View file

@ -43,6 +43,7 @@ angular.module('forms').config(['$stateProvider',
controller: 'SubmitFormController',
controllerAs: 'ctrl'
}).state('viewForm', {
abstract: true,
url: '/forms/:formId/admin',
templateUrl: 'modules/forms/admin/views/admin-form.client.view.html',
data: {
@ -63,18 +64,35 @@ angular.module('forms').config(['$stateProvider',
}]
},
controller: 'AdminFormController'
}).state('viewForm.configure', {
}).state('viewForm.create', {
url: '/create',
templateUrl: 'modules/forms/admin/views/adminTabs/create.html'
})
.state('viewForm.configure', {
abstract: true,
url: '/configure',
templateUrl: 'modules/forms/admin/views/adminTabs/configure.html'
}).state('viewForm.design', {
}).state('viewForm.configure.general', {
url: '/general',
templateUrl: 'modules/forms/admin/views/adminTabs/configureTabs/general.html'
}).state('viewForm.configure.self_notifications', {
url: '/self_notifications',
templateUrl: 'modules/forms/admin/views/adminTabs/configureTabs/self-notifications.html'
}).state('viewForm.configure.respondent_notifications', {
url: '/respondent_notifications',
templateUrl: 'modules/forms/admin/views/adminTabs/configureTabs/respondent-notifications.html'
})
.state('viewForm.design', {
url: '/design',
templateUrl: 'modules/forms/admin/views/adminTabs/design.html'
}).state('viewForm.share', {
url: '/share',
templateUrl: 'modules/forms/admin/views/adminTabs/share.html'
}).state('viewForm.analyze', {
url: '/analyze',
templateUrl: 'modules/forms/admin/views/adminTabs/analyze.html'
}).state('viewForm.create', {
url: '/create',
templateUrl: 'modules/forms/admin/views/adminTabs/create.html'
});
}
]);

View file

@ -3,5 +3,10 @@
// Use Application configuration module to register a new module
ApplicationConfiguration.registerModule('forms', [
'ngFileUpload', 'ui.date', 'ui.sortable',
<<<<<<< HEAD
'angular-input-stars', 'users', 'ngclipboard', 'textAngular',
'frapontillo.bootstrap-switch'
=======
'angular-input-stars', 'users', 'ngclipboard'
>>>>>>> 2.20
]);//, 'colorpicker.module' @TODO reactivate this module

View file

@ -10,7 +10,8 @@
$httpBackend,
$stateParams,
$location,
$state;
$state,
$timeout;
var sampleUser = {
firstName: 'Full',
@ -175,7 +176,7 @@
// 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, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) {
beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms, _$timeout_) {
// Set a new global scope
scope = $rootScope.$new();
@ -187,6 +188,7 @@
$httpBackend = _$httpBackend_;
$location = _$location_;
$state = _$state_;
$timeout = _$timeout_;
$httpBackend.whenGET(/\.html$/).respond('');
$httpBackend.whenGET('/users/me/').respond('');
@ -197,60 +199,70 @@
};
}));
it('AdminFormController should fetch current Form when instantiated', function() {
// Run controller functionality
var controller = createAdminFormController();
it('AdminFormController should fetch current Form when instantiated', inject(function($timeout) {
$timeout(function() {
// Run controller functionality
var controller = createAdminFormController();
// Test scope value
expect(scope.myform).toEqualData(sampleForm);
});
it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', inject(function($uibModal) {
var controller = createAdminFormController();
//Set $state transition
$state.expectTransitionTo('listForms');
// Set DELETE response
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
//Run controller functionality
scope.openDeleteModal();
scope.removeCurrentForm();
$httpBackend.flush();
$state.ensureAllTransitionsHappened();
// Test scope value
expect(scope.myform).toEqualData(sampleForm);
});
}));
it('$scope.update() should send a PUT request with the id of form', function() {
var controller = createAdminFormController();
it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', inject(function($timeout, $uibModal) {
$timeout(function() {
var controller = createAdminFormController();
//Set PUT response
$httpBackend.expect('PUT', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
//Set $state transition
$state.expectTransitionTo('listForms');
//Run controller functionality
scope.update(false, sampleForm, false, false);
// Set DELETE response
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
$httpBackend.flush();
});
//Run controller functionality
scope.openDeleteModal();
scope.removeCurrentForm();
it('$scope.openDeleteModal() should open scope.deleteModal', function() {
var controller = createAdminFormController();
$httpBackend.flush();
$state.ensureAllTransitionsHappened();
});
}));
//Run controller functionality
scope.openDeleteModal();
expect(scope.deleteModal.opened).toEqual(true);
});
it('$scope.update() should send a PUT request with the id of form', inject(function($timeout) {
$timeout(function() {
var controller = createAdminFormController();
it('$scope.cancelDeleteModal() should close $scope.deleteModal', inject(function($uibModal) {
var controller = createAdminFormController();
//Set PUT response
$httpBackend.expect('PUT', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
//Run controller functionality
scope.openDeleteModal();
//Run controller functionality
scope.update(false, sampleForm, false, false);
//Run controller functionality
scope.cancelDeleteModal();
expect( scope.deleteModal.opened ).toEqual(false);
$httpBackend.flush();
});
}));
it('$scope.openDeleteModal() should open scope.deleteModal', inject(function($timeout) {
$timeout(function() {
var controller = createAdminFormController();
//Run controller functionality
scope.openDeleteModal();
expect(scope.deleteModal.opened).toEqual(true);
});
}));
it('$scope.cancelDeleteModal() should close $scope.deleteModal', inject(function($uibModal, $timeout) {
$timeout(function() {
var controller = createAdminFormController();
//Run controller functionality
scope.openDeleteModal();
//Run controller functionality
scope.cancelDeleteModal();
expect( scope.deleteModal.opened ).toEqual(false);
});
}));
});
}());

View file

@ -11,7 +11,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
$scope.signin = function() {
if($scope.forms && $scope.forms.hasOwnProperty('siginForm') && !$scope.forms.signinForm.$invalid){
if($scope.forms && $scope.forms.signinForm.$valid){
User.login($scope.credentials).then(
function(response) {
Auth.login(response);
@ -40,7 +40,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
return;
}
if(!$scope.forms.signupForm.$invalid){
if($scope.forms && $scope.forms.signupForm.$valid){
User.signup($scope.credentials).then(
function(response) {
$state.go('signup-success');