Merge remote-tracking branch 'origin/fixUXBugs' into 2.20

This commit is contained in:
David Baldwynn 2017-11-15 14:49:28 -05:00
commit c911e53609
61 changed files with 876 additions and 550 deletions

View file

@ -81,10 +81,14 @@ exports.createSubmission = function(req, res) {
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 (form.selfNotifications && form.selfNotifications.enabled) {
if(form.selfNotifications.fromField){
form.selfNotifications.fromEmails = formFieldDict[form.selfNotifications.fromField];
} else {
form.selfNotifications.fromEmails = config.mailer.options.from;
}
emailNotifications.send(form.selfNotifications, formFieldDict, smtpTransport, function(err){
if(err){
return callback({
message: 'Failure sending submission self-notification email'
@ -102,7 +106,7 @@ exports.createSubmission = function(req, res) {
form.respondentNotifications.toEmails = formFieldDict[form.respondentNotifications.toField];
debugger;
emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, constants.varFormat, function(err){
emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, function(err){
if(err){
return callback({
message: 'Failure sending submission respondent-notification email'
@ -196,13 +200,21 @@ exports.getVisitorData = function(req, res) {
responses: "$responses",
visits: "$visits",
average_time: {
$divide : ["$total_time", "$responses"]
$cond: [
{ $eq: [ "$responses", 0 ] },
0,
{ $divide: ["$total_time", "$responses"] }
]
},
conversion_rate: {
$multiply: [
100,
{
$divide : ["$responses", "$visits"]
$cond: [
{ $eq: [ "$visits", 0 ] },
0,
{ $divide: ["$responses", "$visits"] }
]
}
]
}
@ -252,13 +264,21 @@ exports.getVisitorData = function(req, res) {
responses: "$responses",
visits: "$visits",
average_time: {
$divide : ["$total_time", "$responses"]
$cond: [
{ $eq: [ "$responses", 0 ] },
0,
{ $divide: ["$total_time", "$responses"] }
]
},
conversion_rate: {
$multiply: [
100,
{
$divide : ["$responses", "$visits"]
$cond: [
{ $eq: [ "$visits", 0 ] },
0,
{ $divide: ["$responses", "$visits"] }
]
}
]
}
@ -499,7 +519,7 @@ exports.formByID = function(req, res, next, id) {
}
Form.findById(id)
.select('admin title language form_fields startPage endPage hideFooter isLive design analytics.gaCode respondentNotifications selfNotifications')
.select('admin title language form_fields startPage endPage showFooter isLive design analytics.gaCode respondentNotifications selfNotifications')
.populate('admin')
.exec(function(err, form) {
if (err) {
@ -528,7 +548,7 @@ exports.formByIDFast = function(req, res, next, id) {
}
Form.findById(id)
.lean()
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode selfNotifications respondentNotifications')
.select('title language form_fields startPage endPage showFooter isLive design analytics.gaCode selfNotifications respondentNotifications')
.exec(function(err, form) {
if (err) {
return next(err);

View file

@ -126,7 +126,6 @@ exports.signup = function(req, res) {
// Then save the temporary user
nev.createTempUser(user, function (err, existingPersistentUser, newTempUser) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});

View file

@ -113,7 +113,6 @@ exports.forgot = function(req, res) {
}
], function(err, obfuscatedEmail) {
if (err) {
console.log(err);
return res.status(400).send({
message: 'Couldn\'t send reset password email due to internal server errors. Please contact support at team@tellform.com.'
});

View file

@ -74,8 +74,5 @@ module.exports = {
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,}))$/,
templateVariable: /<var(.*)id(.*)>(.|\n)*?<\/var>/g
},
varFormat: ['<var([^<>]+)id=["\']{1}field:', '["\']{1}>([^<>]+)*?<\/var>'],
}
};

View file

@ -1,9 +1,12 @@
'use strict';
const jsdom = require("jsdom");
var JSDOM = jsdom.JSDOM;
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);
send: function(emailSettings, emailTemplateVars, smtpTransport, cb){
var parsedTemplate = this.parseTemplate(emailSettings.htmlTemplate, emailTemplateVars, false);
var parsedSubject = this.parseTemplate(emailSettings.subject, emailTemplateVars, true);
var mailOptions = {
replyTo: emailSettings.fromEmails,
from: 'noreply@tellform.com',
@ -12,31 +15,41 @@ module.exports = {
html: parsedTemplate
};
console.log('HERE');
smtpTransport.sendMail(mailOptions, function(){
console.log('THERE');
cb();
smtpTransport.sendMail(mailOptions, function(err){
cb(err);
});
},
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;
},
parseTemplate: function(emailTemplate, emailTemplateVars, onlyText){
var dom = new JSDOM('<!doctype html>'+emailTemplate);
replaceTemplateVal: function(key, val, template, varFormat){
return template.replace( new RegExp(varFormat[0] + key + varFormat[1], 'g'), val);
Object.keys(emailTemplateVars).forEach(function (key) {
var elem = dom.window.document.querySelector("span.placeholder-tag[data-id='" + key + "']");
if(elem !== null){
elem.outerHTML = emailTemplateVars[key];
}
});
//Removed unused variables
//TODO: Currently querySelectorAll not working in JSDOM
/*
dom.window.document.querySelectorAll("span[data-id]").forEach(function(elem){
if(elem !== null){
elem.outerHTML = '';
}
})
*/
if(onlyText){
return dom.window.document.documentElement.textContent;
}
return dom.serialize();
},
createFieldDict: function(form_fields){
var formFieldDict = {};
form_fields.forEach(function(field){
if(field.hasOwnProperty('globalId') && field.hasOwnProperty('fieldValue')){
formFieldDict[field.globalId] = field.fieldValue;
formFieldDict[field.globalId+''] = field.fieldValue+'';
}
});
return formFieldDict;

View file

@ -192,9 +192,9 @@ var FormSchema = new Schema({
}
},
hideFooter: {
showFooter: {
type: Boolean,
default: false
default: true
},
isLive: {
@ -264,6 +264,16 @@ function formFieldsAllHaveIds(form_fields){
return true;
}
FormSchema.pre('save', function (next) {
if(this.form_fields && this.form_fields.length){
this.form_fields = this.form_fields.filter(function(field){
return !field.deletePreserved;
});
}
next();
});
/*
FormSchema.pre('save', function (next) {
var that = this;
var _original;
@ -374,6 +384,7 @@ FormSchema.pre('save', function (next) {
next();
});
});
*/
FormSchema.index({created: 1});

View file

@ -176,6 +176,7 @@ describe('FormSubmission Model Unit Tests:', function() {
});
});
/*
describe('Test FormField and Submission Logic', function() {
beforeEach(function(done){
@ -245,6 +246,7 @@ describe('FormSubmission Model Unit Tests:', function() {
});
});
});
*/
afterEach(function(done) {
Form.remove().exec(function() {

View file

@ -3,9 +3,11 @@
/**
* Module dependencies.
*/
const emailNotifications = require('../../libs/send-email-notifications'),
constants = require('../../libs/constants'),
mockTransport = require("nodemailer").createTransport("Stub"),
const should = require('should'),
emailNotifications = require('../../libs/send-email-notifications'),
mockTransport = require('nodemailer').createTransport({
jsonTransport: true
}),
config = require('../../../config/config');
/**
@ -20,7 +22,7 @@ const validFormFields = [
const validFieldDict = {
'56340745f59a6fc9e22028e9': 'John Smith',
'5c9e22028e907634f45f59a6': 'https://johnsmith.me',
'56e90745f5934fc9e22028a6': 45
'56e90745f5934fc9e22028a6': '45'
};
const invalidFormFields = [
@ -29,13 +31,11 @@ const invalidFormFields = [
{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 htmlTemplate = '<p><span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>'+
'<br><span class="placeholder-tag" data-id="5c9e22028e907634f45f59a6">Your Website</span>'+
'<br><span class="placeholder-tag" data-id="56e90745f5934fc9e22028a6">Your Age</span></p>';
const renderedTemplate = '<p>John Smith \
<br>https://johnsmith.me \
<br>45</p>';
const renderedTemplate = '<!DOCTYPE html><html><head></head><body><p>John Smith<br>https://johnsmith.me<br>45</p></body></html>';
/**
* Unit tests
@ -56,36 +56,24 @@ describe('Send Email Notification Unit Tests', function() {
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),'');
var actualRenderedTemplate = emailNotifications.parseTemplate(htmlTemplate, validFieldDict, false).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>!',
subject: 'Hello <span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>!',
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){
emailNotifications.send(emailSettings, emailTemplateVars, mockTransport, function(err){
should.not.exist(err);
done();
});

View file

@ -59,7 +59,6 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
script(type='text/javascript').
socketUrl = "!{socketUrl}"
script(src='/static/lib/jquery/dist/jquery.min.js', type='text/javascript')
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')

View file

@ -3,9 +3,9 @@ extends layout.server.view.pug
block content
section.content(ui-view='', ng-cloak='')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.snow.min.css')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.bubble.min.css')
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').
@ -44,6 +44,10 @@ block content
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.min.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/ng-quill/3.5.1/ng-quill.js')
script(src='https://unpkg.com/quill-placeholder-module@0.2.0/dist/placeholder-module.js')
//Application JavaScript Files
each jsFile in jsFiles
script(type='text/javascript', src=jsFile)

View file

@ -16,13 +16,11 @@
"angular-mocks": "~1.4.7",
"angular-bootstrap": "~0.14.3",
"angular-ui-utils": "~3.0.0",
"angular-ui-router": "~0.2.11",
"ng-file-upload": "^12.0.4",
"angular-raven": "~0.5.11",
"angular-ui-date": "~0.0.11",
"lodash": "~3.10.0",
"angular-ui-sortable": "~0.13.4",
"angular-permission": "~1.1.1",
"file-saver.js": "~1.20150507.2",
"angular-bootstrap-colorpicker": "~3.0.19",
"angular-scroll": "^1.0.0",
@ -41,21 +39,17 @@
"socket.io-client": "^1.7.2",
"css-toggle-switch": "^4.0.2",
"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"
"jquery": "^3.2.1",
"ng-quill": "https://github.com/KillerCodeMonkey/ng-quill",
"angular-ui-router": "^1.0.11",
"angular-permission": "^5.3.2"
},
"resolutions": {
"angular-bootstrap": "^0.14.0",
"angular": "1.4.14",
"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-ui-select": "^0.19.8",
"jquery": "^3.2.1"
"jquery": "^3.2.1",
"angular-ui-router": "^1.0.11"
},
"overrides": {
"BOWER-PACKAGE": {

242
package-lock.json generated
View file

@ -4,6 +4,16 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "8.0.52",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.52.tgz",
"integrity": "sha512-wOU/VRodnI/4Chxuu6R6bcyN9aE3rztO0i8R76PZO7+DxTXWy60nseGN4ujspucmxrfj5mzgCYPXiXqrD6KC3Q=="
},
"abab": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -304,6 +314,11 @@
"resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
"integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE="
},
"array-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
},
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@ -3194,6 +3209,11 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"content-type-parser": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz",
"integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ=="
},
"convert-source-map": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
@ -3545,6 +3565,19 @@
"parserlib": "0.2.5"
}
},
"cssom": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs="
},
"cssstyle": {
"version": "0.2.37",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz",
"integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=",
"requires": {
"cssom": "0.3.2"
}
},
"ctype": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz",
@ -3639,8 +3672,7 @@
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"degenerator": {
"version": "1.0.4",
@ -3767,6 +3799,11 @@
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
},
"domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.0.tgz",
"integrity": "sha512-WpwuBlZ2lQRFa4H/4w49deb9rJLot9KmqrKKjMc9qBl7CID+DdC2swoa34ccRl+anL2B6bLp6TjFdIdnzekMBQ=="
},
"domhandler": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
@ -4160,8 +4197,7 @@
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"etag": {
"version": "1.7.0",
@ -4533,8 +4569,7 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"faye-websocket": {
"version": "0.4.4",
@ -7062,6 +7097,14 @@
"core-util-is": "1.0.2"
}
},
"html-encoding-sniffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
"requires": {
"whatwg-encoding": "1.0.3"
}
},
"html-minifier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.0.3.tgz",
@ -7893,6 +7936,78 @@
"integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==",
"dev": true
},
"jsdom": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.3.0.tgz",
"integrity": "sha512-aPZTDl4MplzQhx5bLztk6nzjbEslmO3Q3+z0WpCMutL1XJDhZIRzir6R1Y8S84LgeT/7jhQvgtUMkY6oPwvlUw==",
"requires": {
"abab": "1.0.4",
"acorn": "5.2.1",
"acorn-globals": "4.1.0",
"array-equal": "1.0.0",
"content-type-parser": "1.0.2",
"cssom": "0.3.2",
"cssstyle": "0.2.37",
"domexception": "1.0.0",
"escodegen": "1.9.0",
"html-encoding-sniffer": "1.0.2",
"nwmatcher": "1.4.3",
"parse5": "3.0.3",
"pn": "1.0.0",
"request": "2.83.0",
"request-promise-native": "1.0.5",
"sax": "1.2.4",
"symbol-tree": "3.2.2",
"tough-cookie": "2.3.3",
"webidl-conversions": "4.0.2",
"whatwg-encoding": "1.0.3",
"whatwg-url": "6.3.0",
"xml-name-validator": "2.0.1"
},
"dependencies": {
"acorn": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz",
"integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w=="
},
"acorn-globals": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz",
"integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==",
"requires": {
"acorn": "5.2.1"
}
},
"escodegen": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz",
"integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==",
"requires": {
"esprima": "3.1.3",
"estraverse": "4.2.0",
"esutils": "2.0.2",
"optionator": "0.8.2",
"source-map": "0.5.7"
}
},
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
},
"estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"optional": true
}
}
},
"jshint": {
"version": "2.9.5",
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz",
@ -8502,7 +8617,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "1.1.2",
"type-check": "0.3.2"
@ -8761,6 +8875,11 @@
"integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
"dev": true
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
},
"log-driver": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz",
@ -9849,6 +9968,11 @@
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"nwmatcher": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz",
"integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw=="
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
@ -9916,7 +10040,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "0.1.3",
"fast-levenshtein": "2.0.6",
@ -9929,8 +10052,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
}
}
},
@ -10093,6 +10215,14 @@
"error-ex": "1.3.1"
}
},
"parse5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
"requires": {
"@types/node": "8.0.52"
}
},
"parsejson": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
@ -10492,11 +10622,15 @@
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.3.tgz",
"integrity": "sha1-ZGx3ARiZhwtqCQPnXpl+jlHadGE="
},
"pn": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.0.0.tgz",
"integrity": "sha1-HPWjCw2AbNGPiPxBprXUrWFbO6k="
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-http": {
"version": "1.0.4",
@ -11286,6 +11420,24 @@
"throttleit": "1.0.0"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
"requires": {
"lodash": "4.17.4"
}
},
"request-promise-native": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
"requires": {
"request-promise-core": "1.1.1",
"stealthy-require": "1.1.1",
"tough-cookie": "2.3.3"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -11386,6 +11538,11 @@
"graceful-fs": "1.2.3"
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"scandirectory": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz",
@ -11931,6 +12088,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
@ -12144,6 +12306,11 @@
"upper-case": "1.1.3"
}
},
"symbol-tree": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
},
"taskgroup": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz",
@ -12289,6 +12456,21 @@
"punycode": "1.4.1"
}
},
"tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
"requires": {
"punycode": "2.1.0"
},
"dependencies": {
"punycode": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
"integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0="
}
}
},
"transformers": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz",
@ -12364,7 +12546,6 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "1.1.2"
}
@ -12723,6 +12904,36 @@
"typechecker": "2.1.0"
}
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
},
"whatwg-encoding": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz",
"integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==",
"requires": {
"iconv-lite": "0.4.19"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
}
}
},
"whatwg-url": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.3.0.tgz",
"integrity": "sha512-rM+hE5iYKGPAOu05mIdJR47pYSR2vDzfrTEFRc/S8D3L60yW8BuXmUJ7Kog7x/DrokFN7JNaHKadpzjouKRRAw==",
"requires": {
"lodash.sortby": "4.7.0",
"tr46": "1.0.1",
"webidl-conversions": "4.0.2"
}
},
"which": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz",
@ -12887,6 +13098,11 @@
"resolved": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz",
"integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0="
},
"xml-name-validator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
"integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU="
},
"xmlhttprequest-ssl": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz",

View file

@ -54,6 +54,7 @@
"helmet": "3.5.0",
"i18n": "^0.8.3",
"jit-grunt": "^0.9.1",
"jsdom": "^11.3.0",
"lodash": "^4.17.4",
"main-bower-files": "~2.9.0",
"method-override": "~2.3.0",

View file

@ -65,8 +65,6 @@ angular.module('view-form').directive('onEnterKey', ['$rootScope', function($roo
var keyCode = event.which || event.keyCode;
if(keyCode === 9 && event.shiftKey) {
console.log('onTabAndShiftKey');
event.preventDefault();
$rootScope.$apply(function() {
$rootScope.$eval($attrs.onTabAndShiftKey);

View file

@ -15,18 +15,12 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
templateUrl: 'form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html',
restrict: 'E',
scope: {
myform:'=',
ispreview: '='
myform:'='
},
controller: function($document, $window, $scope){
var FORM_ACTION_ID = 'submit_field';
$scope.forms = {};
//Don't start timer if we are looking at a design preview
if($scope.ispreview){
TimeCounter.restartClock();
}
var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){
return field.fieldType !== 'statement';
}).length;
@ -35,8 +29,7 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
$scope.myform.visible_form_fields = $scope.myform.form_fields.filter(function(field){
return !field.deletePreserved;
});
console.log($scope.myform.visible_form_fields);
})
});
$scope.updateFormValidity = function(){
$timeout(function(){
@ -320,12 +313,11 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
setTimeout(function () {
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
.success(function (data, status) {
.then(function (data, status) {
$scope.myform.submitted = true;
$scope.loading = false;
SendVisitorData.send(form, getActiveField(), _timeElapsed);
})
.error(function (error) {
}, function (error) {
$scope.loading = false;
console.error(error);
$scope.error = error.message;

View file

@ -72,7 +72,7 @@
</form>
</div>
<section ng-if="!myform.hideFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
<section ng-if="myform.showFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
<div class="container-fluid">
<div class="row">
<div class="col-sm-5 col-md-6 col-xs-5" ng-show="!myform.submitted">

View file

@ -8,6 +8,9 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
}
]);
var statesWithoutAuth = ['signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
var statesToIgnore = statesWithoutAuth.concat(['', 'home']);
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
function($rootScope, Auth, $state, $stateParams) {
@ -21,10 +24,8 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
params: fromParams
}
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
//Redirect to listForms if user is authenticated
if(statesToIgnore.indexOf(toState.name) > 0){
if(statesToIgnore.indexOf(toState.name) > -1){
if(Auth.isAuthenticated()){
event.preventDefault(); // stop current execution
$state.go('listForms'); // go to listForms page
@ -35,7 +36,6 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
event.preventDefault(); // stop current execution
$state.go('listForms'); // go to listForms page
}
});
}
@ -44,22 +44,28 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
//Page access/authorization logic
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
$rootScope.$on('$stateChangeStart', function(event, next) {
var authenticator, permissions, user;
permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if(statesWithoutAuth.indexOf(toState.name) === -1){
Auth.ensureHasCurrentUser(User).then(
function onSuccess(currentUser){
if(currentUser){
var authenticator = new Authorizer(user);
var permissions = toState && toState.data && toState.data.permissions ? toState.data.permissions : null;
Auth.ensureHasCurrentUser(User);
user = Auth.currentUser;
if(user){
authenticator = new Authorizer(user);
if( (permissions !== null) ){
if( !authenticator.canAccess(permissions) ){
if( permissions !== null && !authenticator.canAccess(permissions) ){
event.preventDefault();
$state.go('access_denied');
}
}
},
function onError(error){
event.preventDefault();
$state.go('access_denied');
}
}
);
} else {
event.preventDefault();
$state.go('access_denied');
}
});
}]);
}]);

View file

@ -6,11 +6,8 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
$rootScope.signupDisabled = $window.signupDisabled;
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User);
$scope.authentication = $rootScope.authentication = Auth;
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
//Set global app language
$rootScope.language = $scope.user.language;
$translate.use($scope.user.language);
@ -19,16 +16,31 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
$rootScope.hideNav = false;
$scope.menu = Menus.getMenu('topbar');
$rootScope.languages = ['en', 'fr', 'es', 'it', 'de'];
$rootScope.langCodeToWord = {
'en': 'English',
'fr': 'Français',
'es': 'Español',
'it': 'Italiàno',
'de': 'Deutsch'
};
$rootScope.wordToLangCode = {
'English': 'en',
'Français': 'fr',
'Español': 'es',
'Italiàno': 'it',
'Deutsch': 'de'
};
$scope.signout = function() {
var promise = User.logout();
promise.then(function() {
Auth.logout();
Auth.ensureHasCurrentUser(User);
$scope.user = $rootScope.user = null;
$state.go('listForms');
//Refresh view
$state.reload();
$state.go('signin', { reload: true });
},
function(reason) {
console.error('Logout Failed: ' + reason);

View file

@ -53,7 +53,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
},
{
heading: $filter('translate')('SHARE_TAB'),
route: 'viewForm.share',
route: 'viewForm.share.share_form',
active: false
},
{

View file

@ -1,13 +1,14 @@
'use strict';
// Forms controller
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal', 'myForms',
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal, myForms) {
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal', 'myForms','$window',
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal, myForms, $window) {
$scope = $rootScope;
$scope.forms = {};
$scope.showCreateModal = false;
$scope.myforms = myForms
$scope.formLanguage = $window.$locale;
$rootScope.languageRegExp = {
regExp: /[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,
@ -74,9 +75,9 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
delete form._id;
$http.post('/forms', {form: form})
.success(function(data, status, headers){
.then(function(data, status, headers){
$scope.myforms.splice(form_index+1, 0, data);
}).error(function(errorResponse){
}, function(errorResponse){
console.error(errorResponse);
if(errorResponse === null){
$scope.error = errorResponse.data.message;
@ -93,10 +94,10 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
if($scope.forms.createForm.$valid && $scope.forms.createForm.$dirty){
$http.post('/forms', {form: form})
.success(function(data, status, headers){
.then(function(data, status, headers){
// Redirect after save
$scope.goToWithId('viewForm.create', data._id+'');
}).error(function(errorResponse){
}, function(errorResponse){
console.error(errorResponse);
$scope.error = errorResponse.data.message;
});
@ -109,10 +110,10 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
}
$http.delete('/forms/'+$scope.myforms[form_index]._id)
.success(function(data, status, headers){
.then(function(data, status, headers){
$scope.myforms.splice(form_index, 1);
$scope.cancelDeleteModal();
}).error(function(error){
}, function(error){
console.error(error);
});
};

View file

@ -1,74 +1,75 @@
.pull-top {
.admin-form .pull-top {
display: inline-block;
vertical-align: top;
float: none;
}
.box {
.admin-form .box {
padding: 0 5px 0 5px!important;
}
.current-fields .field-row {
.admin-form .current-fields .field-row {
padding: 5px 0;
}
.current-fields .panel {
.admin-form .current-fields .panel {
background-color: #f1f1f1;
margin-top: 0!important;
}
.current-fields .panel:hover {
.admin-form .current-fields .panel:hover {
background-color: #fff;
cursor: pointer;
}
.current-fields .panel.tool-panel {
.admin-form .current-fields .panel.tool-panel {
background-color: white;
}
.current-fields .panel-heading {
.admin-form .current-fields .panel-heading {
background-color: #f1f1f1;
position: relative;
}
.current-fields .panel-heading:hover {
.admin-form .current-fields .panel-heading:hover {
background-color: #fff;
cursor: pointer;
}
.current-fields .panel-heading a:hover {
.admin-form .current-fields .panel-heading a:hover {
text-decoration: none;
}
.current-fields .tool-panel.panel:hover {
.admin-form .current-fields .tool-panel.panel:hover {
border-color: #9d9d9d;
background-color: #eee;
cursor: pointer;
}
.current-fields .tool-panel.panel:hover .panel-heading {
.admin-form .current-fields .tool-panel.panel:hover .panel-heading {
background-color: inherit;
color: #000;
cursor: pointer;
}
.current-fields .tool-panel.panel .panel-heading {
.admin-form .current-fields .tool-panel.panel .panel-heading {
background-color: #fff;
color: #9d9d9d;
}
.current-fields .tool-panel.panel .panel-heading a {
.admin-form .current-fields .tool-panel.panel .panel-heading a {
color: inherit;
}
.current-fields .tool-panel.panel .panel-heading a:hover{
.admin-form .current-fields .tool-panel.panel .panel-heading a:hover{
text-decoration: none;
}
/* Custom Tab CSS */
.nav.nav-pills.nav-stacked {
.admin-form .nav.nav-pills.nav-stacked {
width: 16.66666667%;
float: left;
position: relative;
min-height: 1px;
padding-right: 15px;
}
div.tab-content {
.admin-form .tab-content {
width: 83.33333333%;
position: relative;
min-height: 1px;
@ -76,13 +77,13 @@ div.tab-content {
padding-top: 0!important;
}
.panel-default.startPage {
.admin-form .panel-default.startPage {
border-style: dashed;
border-color: #a9a9a9;
border-width:3px;
}
.busy-updating-wrapper {
.admin-form .busy-updating-wrapper {
text-align: center;
font-size: 20px;
position: fixed;
@ -91,7 +92,7 @@ div.tab-content {
z-index: 1;
}
.busy-submitting-wrapper {
.admin-form .busy-submitting-wrapper {
position: fixed;
top: 50%;
left: 0;
@ -99,7 +100,7 @@ div.tab-content {
bottom: 0;
}
.dropzone h4.panel-title {
.admin-form .dropzone h4.panel-title {
height: 17px;
overflow: hidden;
}
@ -142,46 +143,10 @@ div.tab-content {
visibility: visible !important;
border: none;
padding:1px;
background: #000;
background: #000;
background:rgba(0, 0, 0, 0.5) !important;
}
.config-form {
max-width: 100%;
}
.config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
div.config-form .row.field {
padding-top:1.5em;
}
div.config-form > .row > .container:nth-of-type(odd){
border-right: 1px #ddd solid;
}
div.config-form.design > .row > .container:nth-of-type(odd){
border-right: none;
}
div.config-form .row > .field-input {
padding-left:0.1em;
}
div.config-form .row > .field-input label {
padding-left:1.3em;
display: block;
}
/* Styles for form admin view (/forms/:formID/admin) */
.admin-form > .page-header {
padding-bottom: 0;
@ -203,30 +168,6 @@ div.config-form .row.field {
margin-bottom: none;
}
/*Styles for admin view tabs */
.admin-form .tab-content {
padding-top: 3em;
}
/*Styles for submission table*/
.submissions-table .table-outer.row {
margin: 1.5em 0 2em 0!important;
}
.submissions-table .table-outer .col-xs-12 {
padding-left: 0!important;
border:1px solid #ddd;
overflow-x: scroll;
border-radius:3px;
}
.submissions-table .table > thead > tr > th {
min-width:8em;
}
.submissions-table .table > tbody > tr.selected {
background-color:#efefef;
}
/*Styles for add fields tab*/
.admin-form .add-field {
background-color: #ddd;
@ -243,10 +184,6 @@ div.config-form .row.field {
border-radius: 4px;
}
.admin-form .oscar-field-select {
margin: 10px 0 10px;
}
.view-form-btn {
border: none;
}

View file

@ -0,0 +1,79 @@
configure-form-directive .placeholder-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;
display: inline!important;
width: 100%;
}
configure-form-directive .ui-select input.form-control {
height: 34px;
padding: 6px;
}
configure-form-directive .config-form .btn-secondary {
border-color: #DDDDDD;
}
configure-form-directive .notification-toggle.toggle-switch {
margin: 5px 0;
}
/* QuillJS Custom Theming */
configure-form-directive .ql-editor {
background: white;
font-size: 18px;
}
configure-form-directive .ql-picker.ql-placeholder {
width: 118px;
}
configure-form-directive .ql-picker.ql-placeholder > span.ql-picker-label:before {
content: attr(data-before);
}
configure-form-directive .ql-picker.ql-placeholder > span.ql-picker-options > span.ql-picker-item::before {
content: attr(data-label);
}
configure-form-directive .config-form .row.field {
padding-top:1.5em;
}
configure-form-directive .config-form > .row > .container:nth-of-type(odd){
border-right: 1px #ddd solid;
}
configure-form-directive .config-form .row > .field-input {
padding-left:0.1em;
}
configure-form-directive .config-form .row > .field-input label {
padding-left:1.3em;
display: block;
}
configure-form-directive .config-form {
max-width: 100%;
}
configure-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

View file

@ -0,0 +1,33 @@
design-form-directive .ui-select input.form-control {
height: 34px;
padding: 6px;
}
design-form-directive .config-form .btn-secondary {
border-color: #DDDDDD;
}
design-form-directive .config-form .row.field {
padding-top: 1.5em;
}
design-form-directive .config-form > .row > .container{
border-right: 1px #ddd solid;
border-right: none;
}
design-form-directive .config-form {
max-width: 100%;
}
design-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

View file

@ -1,43 +0,0 @@
.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

@ -1,4 +1,19 @@
/*Styles for submission table*/
.submissions-table .table-outer.row {
margin: 1.5em 0 2em 0!important;
}
.submissions-table .table-outer .col-xs-12 {
padding-left: 0!important;
border:1px solid #ddd;
overflow-x: scroll;
border-radius:3px;
}
.submissions-table .table > thead > tr > th {
min-width:8em;
}
.submissions-table .table > tbody > tr.selected {
background-color:#efefef;
}
.analytics .header-title {
font-size: 1em;

View file

@ -0,0 +1,26 @@
share-form-directive #copyEmbedded {
min-height: fit-content;
width: 100%;
background-color: #36404B;
color: white;
padding: 18px;
}
share-form-directive .tab-content {
padding-top: 50px;
}
share-form-directive .config-form {
max-width: 100%;
}
share-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$filter', '$state',
function ($rootScope, $filter, $state) {
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$state', '$translate', '$timeout', '$window',
function ($rootScope, $state, $translate, $timeout, $window) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/configure-form.client.view.html',
restrict: 'E',
@ -14,47 +14,19 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
$scope.resetForm = $rootScope.resetForm;
$scope.update = $rootScope.update;
console.log($scope.myform);
$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
Quill.register('modules/placeholder', PlaceholderModule.default(Quill))
$scope.customModules = {
placeholder: {
placeholders: $scope.myform.visible_form_fields.map(function(field){
return {
id: field.globalId,
label: field.title
};
}),
className: 'placeholder-tag',
delimiters: ['', '']
}
];
};
$scope.emailFields = $scope.myform.form_fields.filter(function(field){
return field.fieldType === 'email';
@ -62,6 +34,25 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
$scope.formHasEmailField = ($scope.emailFields.length > 0);
/* Tab Routing Logic */
$scope.configureTabs = [
{
heading: $translate.instant('GENERAL_TAB'),
route: 'viewForm.configure.general',
active: false
},
{
heading: $translate.instant('SELF_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.self_notifications',
active: false
},
{
heading: $translate.instant('RESPONDENT_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.respondent_notifications',
active: false
}
];
$scope.go = function(tab){
tab.active = true;
$state.go(tab.route);
@ -76,6 +67,14 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
$scope.$on('$viewContentLoaded', function ($evt, data) {
$timeout(function(){
if(!$('.ql-picker.ql-placeholder > span.ql-picker-label').attr('data-before')){
$('.ql-picker.ql-placeholder > span.ql-picker-label').attr('data-before', $translate.instant('ADD_VARIABLE_BUTTON'));
}
}, 500);
});
}
};
}

View file

@ -122,10 +122,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
'Trash': 'Trash Can'
};
if($scope.field.fieldType === 'dropdown' || $scope.field.fieldType === 'radio'){
}
$scope.saveField = function(){
if($scope.options.isEdit){
$scope.myform.form_fields[field_index] = $scope.field;
@ -259,15 +255,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
//Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update;
/*
** FormFields (ui-sortable) drag-and-drop configuration
*/
$scope.dropzone = {
handle: '.handle',
containment: '.dropzoneContainer',
cursor: 'grabbing'
};
/*
** Field CRUD Methods
*/

View file

@ -24,9 +24,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
//Waits until deletionInProgress is false before running getSubmissions
$scope.$watch("deletionInProgress",function(newVal, oldVal){
if(newVal === oldVal) return;
if(newVal === false && $scope.waitingForDeletion) {
if(newVal !== oldVal && newVal === false && $scope.waitingForDeletion) {
$scope.getSubmissions();
$scope.waitingForDeletion = false;
}
@ -80,7 +78,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
var data = response.data || [];
$scope.analyticsData = data[0];
$scope.analyticsData.globalStatistics = $scope.analyticsData.globalStatistics[0];
$scope.analyticsData.globalStatistics = formatGlobalStatistics($scope.analyticsData.globalStatistics);
$scope.analyticsData.deviceStatistics = formatDeviceStatistics($scope.analyticsData.deviceStatistics);
});
};
@ -106,6 +104,19 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
/*
** Analytics Functions
*/
var formatGlobalStatistics = function(globalStatData){
if(!globalStatData.length){
return {
visits: 0,
responses: 0,
conversion_rate: 0,
average_time: 0
};
}
return globalStatData[0];
}
var formatDeviceStatistics = function(deviceStatData){
var newStatItem = function(){
return {
@ -172,14 +183,13 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
method: 'DELETE',
data: {deleted_submissions: delete_ids},
headers: {'Content-Type': 'application/json;charset=utf-8'}
}).success(function(data, status){
}).then(function(data, status){
$scope.deletionInProgress = true;
//Remove deleted ids from table
$scope.table.rows = $scope.table.rows.filter(function(field){
return !field.selected;
});
})
.error(function(err){
}, function(err){
$scope.deletionInProgress = true;
console.error(err);
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('forms').directive('shareFormDirective', ['$rootScope',
function ($rootScope) {
angular.module('forms').directive('shareFormDirective', ['$rootScope', '$translate', '$state',
function ($rootScope, $translate, $state) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/share-form.client.view.html',
restrict: 'E',
@ -10,6 +10,42 @@ angular.module('forms').directive('shareFormDirective', ['$rootScope',
},
controller: function($scope){
$scope.actualFormURL = $scope.actualformurl;
$scope.fullScreen = "<iframe id='iframe' src='" + $scope.actualFormURL + "' style='width:100%;height:500px;'></iframe>"+
"<div style='font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;'>"+
$translate.instant('POWERED_BY')+
"<a href='https://www.tellform.com' style='color: #999' target='_blank'>TellForm</a>"+
"</div>";
/* Tab Routing Logic */
$scope.shareTabs = [
{
heading: $translate.instant('SHARE_YOUR_FORM'),
route: 'viewForm.share.share_form',
active: false
},
{
heading: $translate.instant('EMBED_YOUR_FORM'),
route: 'viewForm.share.embed_form',
active: false
}
];
$scope.go = function(tab){
tab.active = true;
$state.go(tab.route);
};
function setActiveTab() {
$scope.shareTabs.forEach(function(tab) {
tab.active = ($state.current.name === tab.route);
});
}
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
}
};
}

View file

@ -4,7 +4,7 @@
angular.module('forms').service('FormFields', [ '$rootScope', '$translate', 'Auth',
function($rootScope, $translate, Auth) {
var language = Auth.ensureHasCurrentUser().language;
var language = $rootScope.language;
$translate.use(language);
this.types = [

View file

@ -41,7 +41,7 @@
</div>
<div class="col-xs-1 col-sm-2">
<small class="pull-right">
<a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}">
<a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}" target="_blank">
<i class="fa fa-external-link"></i>
<span>
{{ 'VIEW_MY_TELLFORM' | translate }}

View file

@ -22,10 +22,10 @@
<div class="col-sm-8 field-input">
<ui-select ng-model="myform.language" search-enabled="false" theme="selectize">
<ui-select-match>
{{ langCodeToWord[$select.selected] }}
{{ $root.langCodeToWord[$select.selected] }}
</ui-select-match>
<ui-select-choices repeat="language in languages">
<span ng-bind-html="langCodeToWord[language] | highlight: $select.search">
<ui-select-choices repeat="language in $root.languages">
<span ng-bind-html="$root.langCodeToWord[language] | highlight: $select.search">
</span>
</ui-select-choices>
</select>
@ -71,7 +71,7 @@
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox"
bs-switch ng-model="myform.hideFooter"
bs-switch ng-model="myform.showFooter"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>

View file

@ -13,7 +13,7 @@
<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>
<h4>{{ 'ENABLE_RESPONDENT_NOTIFICATIONS' | translate }}</h4>
</div>
<div class="notification-row">
@ -29,7 +29,7 @@
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}:</h5>
<h4>{{ 'SEND_NOTIFICATION_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -47,7 +47,7 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}:</h5>
<h4>{{ 'REPLY_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -60,25 +60,50 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}:</h5>
<h4>{{ 'EMAIL_SUBJECT' | translate }}</h4>
</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>
<ng-quill-editor modules="customModules"
ng-model="myform.respondentNotifications.subject">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
<h4>{{ 'EMAIL_MESSAGE' | translate }}</h4>
</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>
<ng-quill-editor modules="customModules"
ng-model="myform.respondentNotifications.htmlTemplate">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
</span>
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
</div>

View file

@ -8,7 +8,7 @@
</div>
<div class='notification-row'>
<h5>{{ 'ENABLE_SELF_NOTIFICATIONS' | translate }}</h5>
<h4>{{ 'ENABLE_SELF_NOTIFICATIONS' | translate }}</h4>
</div>
<div class='notification-row'>
@ -21,7 +21,7 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}</h5>
<h4>{{ 'SEND_NOTIFICATION_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -34,7 +34,7 @@
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}</h5>
<h4>{{ 'REPLY_TO' | translate }}</h4>
</div>
<div class="col-sm-12 ui-select field-input">
@ -52,22 +52,51 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}</h5>
<h4>{{ 'EMAIL_SUBJECT' | translate }}</h4>
</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>
<ng-quill-editor modules="customModules"
ng-model="myform.selfNotifications.subject">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
<h4>{{ 'EMAIL_MESSAGE' | translate }}</h4>
</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>
<ng-quill-editor modules="customModules"
ng-model="myform.selfNotifications.htmlTemplate">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
</span>
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
</div>

View file

@ -0,0 +1,17 @@
<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">
<div id="copyEmbedded" class="form-control">
{{ fullScreen }}
</div>
</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>

View file

@ -0,0 +1,13 @@
<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>

View file

@ -1,5 +1,4 @@
<div class="config-form container">
<div class="row">
<uib-tabset active="activePill" type="pills">
<uib-tab ng-repeat="tab in configureTabs" active="tab.active" select="go(tab)" heading="{{tab.heading}}">

View file

@ -186,7 +186,6 @@
</div>
</script>
<!-- Edit EndPage Modal Dialog Template -->
<script type="text/ng-template" id="editEndPageModal.html" class="edit-endpage-modal">
<div class="modal-body">
@ -527,7 +526,7 @@
<div class="panel-group dropzone col-xs-12" ui-sortable="sortableOptions" ng-model="myform.form_fields">
<div class="col-xs-12 field-row" ng-repeat="field in myform.form_fields track by $id($index)" ng-if="!field.deletePreserved">
<div class="col-xs-12 field-row" ng-repeat="field in myform.form_fields track by $index" ng-hide="field.deletePreserved">
<div class="col-xs-10">
<div class="panel panel-default" ng-click="openEditModal(field, true, $index)">
<div class="panel-heading">

View file

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

View file

@ -1,44 +1,9 @@
<div class="config-form row">
<div class="config-form container">
<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>
<uib-tabset active="activePill" type="pills">
<uib-tab ng-repeat="tab in shareTabs" active="tab.active" select="go(tab)" heading="{{tab.heading}}">
<div ui-view></div>
</uib-tab>
</uib-tabset>
</div>
</div>

View file

@ -52,9 +52,9 @@
<div class="col-xs-5 field-title text-left"> {{ 'LANGUAGE' | translate }} </div>
<div class="col-xs-12 field-input">
<div class="button custom-select">
<select style="color:black;" name="language" required ng-model="formLanguage" ng-init="formLanguage = user.language">
<option ng-repeat="language in languages" value="{{language}}">
{{language}}
<select style="color:black;" name="language" required ng-model="formLanguage">
<option ng-repeat="language in $root.languages" value="{{language}}">
{{ $root.langCodeToWord[language] }}
</option>
</select>
</div>

View file

@ -55,31 +55,4 @@ 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

@ -84,13 +84,22 @@ angular.module('forms').config(['$stateProvider',
templateUrl: 'modules/forms/admin/views/adminTabs/configureTabs/respondent-notifications.html'
})
.state('viewForm.share', {
abstract: true,
url: '/share',
templateUrl: 'modules/forms/admin/views/adminTabs/share.html'
}).state('viewForm.share.share_form', {
url: '/share_form',
templateUrl: 'modules/forms/admin/views/adminTabs/shareTabs/share_form.html'
}).state('viewForm.share.embed_form', {
url: '/embed_form',
templateUrl: 'modules/forms/admin/views/adminTabs/shareTabs/embed_form.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', {
}).state('viewForm.analyze', {
url: '/analyze',
templateUrl: 'modules/forms/admin/views/adminTabs/analyze.html'
});

View file

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

View file

@ -149,7 +149,6 @@
//var controller = createSubmitFormController();
console.log(vm);
$stateParams.formId = '525a8422f6d0f87f0e407a33';
// Set GET response

View file

@ -31,7 +31,7 @@
startPage: {
showStart: false
},
hideFooter: false,
showFooter: false,
isGenerated: false,
isLive: false,
autofillPDFs: false,

View file

@ -59,7 +59,7 @@
endPage: {
showEnd: false
},
hideFooter: false,
showFooter: false,
isGenerated: false,
isLive: false,
_id: '525a8422f6d0f87f0e407a33'

View file

@ -32,7 +32,7 @@
showStart: false,
buttons: []
},
hideFooter: false,
showFooter: false,
isGenerated: false,
isLive: false,
_id: '525a8422f6d0f87f0e407a33'

View file

@ -52,7 +52,7 @@ var MobileDetect = function(userAgentStr){
startPage: {
showStart: false
},
hideFooter: false,
showFooter: false,
isGenerated: false,
isLive: false,
autofillPDFs: false,

View file

@ -4,26 +4,25 @@
angular.module('users').config(['$stateProvider',
function($stateProvider) {
var checkLoggedin = function($q, $timeout, $state, User, Auth) {
var deferred = $q.defer();
if (Auth.currentUser && Auth.currentUser.email) {
$timeout(deferred.resolve);
}
else {
Auth.currentUser = User.getCurrent(
function() {
Auth.login();
$timeout(deferred.resolve());
},
function() {
Auth.logout();
$timeout(deferred.reject());
$state.go('signin', {reload: true});
});
}
var checkCurrentUser = function($q, $state, User, Auth) {
var deferred = $q.defer();
if (Auth.currentUser && Auth.currentUser.email) {
deferred.resolve(Auth.currentUser);
} else {
User.getCurrent().then(
function(user) {
Auth.login();
deferred.resolve(user);
},
function() {
Auth.logout();
deferred.reject();
$state.go('signin', {reload: true});
});
}
return deferred.promise;
return deferred.promise;
};
var checkSignupDisabled = function($window, $timeout, $q) {
@ -40,22 +39,24 @@ angular.module('users').config(['$stateProvider',
$stateProvider.
state('profile', {
resolve: {
loggedin: checkLoggedin
currentUser: ['$q', '$state', 'User', 'Auth', checkCurrentUser]
},
url: '/settings/profile',
templateUrl: 'modules/users/views/settings/edit-profile.client.view.html'
templateUrl: 'modules/users/views/settings/edit-profile.client.view.html',
controller: 'SettingsController'
}).
state('password', {
resolve: {
loggedin: checkLoggedin
},
currentUser: ['$q', '$state', 'User', 'Auth', checkCurrentUser]
},
url: '/settings/password',
templateUrl: 'modules/users/views/settings/change-password.client.view.html'
templateUrl: 'modules/users/views/settings/change-password.client.view.html',
controller: 'SettingsController'
}).
state('accounts', {
resolve: {
loggedin: checkLoggedin
},
currentUser: ['$q', '$state', 'User', 'Auth', checkCurrentUser]
},
url: '/settings/accounts',
templateUrl: 'modules/users/views/settings/social-accounts.client.view.html'
}).

View file

@ -17,7 +17,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
Auth.login(response);
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User);
if(statesToIgnore.indexOf($state.previous.state.name) === -1) {
if($state.previous && statesToIgnore.indexOf($state.previous.state.name) === -1) {
$state.go($state.previous.state.name, $state.previous.params);
} else {
$state.go('listForms');

View file

@ -2,7 +2,6 @@
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User', '$translate', '$window',
function($scope, $stateParams, $state, User, $translate, $window) {
$translate.use($window.locale);
$scope.error = '';
$scope.forms = {};

View file

@ -1,9 +1,9 @@
'use strict';
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth',
function($scope, $rootScope, $http, $state, Users, Auth) {
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth', 'currentUser',
function($scope, $rootScope, $http, $state, Users, Auth, currentUser) {
$scope.user = Auth.currentUser;
$scope.user = currentUser;
// Check if there are additional accounts
$scope.hasConnectedAdditionalSocialAccounts = function(provider) {
@ -30,12 +30,12 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
params: {
provider: provider
}
}).success(function(response) {
}).then(function(response) {
// If successful show success message and clear form
$scope.success = true;
$scope.error = null;
$scope.user = response;
}).error(function(response) {
}, function(response) {
$scope.success = null;
$scope.error = response.message;
});
@ -64,12 +64,12 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
$scope.changeUserPassword = function() {
$scope.success = $scope.error = null;
$http.post('/users/password', $scope.passwordDetails).success(function(response) {
$http.post('/users/password', $scope.passwordDetails).then(function(response) {
// If successful show success message and clear form
$scope.success = true;
$scope.error = null;
$scope.passwordDetails = null;
}).error(function(response) {
}, function(response) {
$scope.success = null;
$scope.error = response.message;
});

View file

@ -29,7 +29,6 @@ angular.module('users').controller('VerifyController', ['$scope', '$state', '$ro
//Validate Verification Token
$scope.validateVerifyToken = function() {
if($stateParams.token){
console.log($stateParams.token);
User.validateVerifyToken($stateParams.token).then(
function(response){
$scope.success = response.message;

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('users').factory('Auth', ['$window',
function($window) {
angular.module('users').factory('Auth', ['$window', '$q',
function($window, $q) {
var userState = {
isLoggedIn: false
@ -17,26 +17,30 @@ angular.module('users').factory('Auth', ['$window',
// because that would create a circular dependency
// Auth <- $http <- $resource <- LoopBackResource <- User <- Auth
ensureHasCurrentUser: function(User) {
var deferred = $q.defer();
if (service._currentUser && service._currentUser.username) {
return service._currentUser;
deferred.resolve(service._currentUser);
} else if ($window.user){
service._currentUser = $window.user;
return service._currentUser;
deferred.resolve(service._currentUser)
} else {
User.getCurrent().then(function(user) {
// success
service._currentUser = user;
userState.isLoggedIn = true;
$window.user = service._currentUser;
return service._currentUser;
deferred.resolve(service._currentUser);
},
function(response) {
userState.isLoggedIn = false;
service._currentUser = null;
$window.user = null;
return null;
deferred.reject('User data could not be fetched from server');
});
}
return deferred.promise;
},
isAuthenticated: function() {

View file

@ -6,19 +6,16 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
var userService = {
getCurrent: function() {
var deferred = $q.defer();
$http.get('/users/me')
.success(function(response) {
deferred.resolve(response);
})
.error(function() {
.then(function(response) {
deferred.resolve(response.data);
}, function() {
deferred.reject('User\'s session has expired');
});
return deferred.promise;
},
login: function(credentials) {
var deferred = $q.defer();
$http.post('/auth/signin', credentials).then(function(response) {
deferred.resolve(response.data);
@ -29,7 +26,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
return deferred.promise;
},
logout: function() {
var deferred = $q.defer();
$http.get('/auth/signout').then(function(response) {
deferred.resolve(null);
@ -40,7 +36,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
return deferred.promise;
},
signup: function(credentials) {
var deferred = $q.defer();
$http.post('/auth/signup', credentials).then(function(response) {
// If successful we assign the response to the global user model
@ -53,7 +48,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
},
resendVerifyEmail: function(_email) {
var deferred = $q.defer();
$http.post('/auth/verify', {email: _email}).then(function(response) {
deferred.resolve(response.data);
@ -65,7 +59,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
},
validateVerifyToken: function(token) {
//DAVID: TODO: The valid length of a token should somehow be linked to server config values
//DAVID: TODO: SEMI-URGENT: Should we even be doing this?
var validTokenRe = /^([A-Za-z0-9]{48})$/g;
@ -82,7 +75,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
},
resetPassword: function(passwordDetails, token) {
var deferred = $q.defer();
$http.post('/auth/reset/'+token, passwordDetails).then(function(response) {
deferred.resolve(response);
@ -95,7 +87,6 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
// Submit forgotten password account id
askForPasswordReset: function(credentials) {
var deferred = $q.defer();
$http.post('/auth/forgot', credentials).then(function(response) {
// Show user success message and clear form

View file

@ -1,6 +1,6 @@
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
<section class="row" data-ng-controller="SettingsController">
<section class="row">
<h3 class="col-md-12 text-center">{{ 'CHANGE_PASSWORD' | translate }}</h3>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6">
<form data-ng-submit="changeUserPassword()" class="signin form-horizontal" autocomplete="off">

View file

@ -1,6 +1,7 @@
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.8.5/css/selectize.default.css'>
<section class="row" data-ng-controller="SettingsController">
<section class="row">
<h2 class="col-xs-offset-1 col-xs-10 text-center">{{ 'EDIT_PROFILE' | translate }}</h2>
<div class="col-xs-offset-3 col-xs-6">
<form name="userForm" data-ng-submit="updateUserProfile(userForm.$valid)" class="signin form-horizontal" autocomplete="off">
@ -15,7 +16,7 @@
<div class="form-group row">
<div class="col-xs-7 field-title">
{{ 'FIRST_NAME_LABEL' | translate }}
<h4>{{ 'FIRST_NAME_LABEL' | translate }}</h4>
</div>
<div class="col-xs-12 field-input">
<input type="text" id="firstName" name="firstName" class="form-control" data-ng-model="user.firstName" ng-pattern="/^[\w0-9 \-.]*$/">
@ -23,7 +24,7 @@
</div>
<div class="form-group row">
<div class="col-xs-7 field-title">
{{ 'LAST_NAME_LABEL' | translate }}
<h4>{{ 'LAST_NAME_LABEL' | translate }}</h4>
</div>
<div class="col-xs-12 field-input">
<input type="text" id="lastName" name="lastName" class="form-control" data-ng-model="user.lastName" ng-pattern="/^[\w0-9 \-.]*$/">
@ -32,24 +33,27 @@
<div class="row">
<hr>
</div>
<div class="row form-group">
<div class="col-xs-7 field-title">
{{ 'LANGUAGE_LABEL' | translate }}
</div>
<div class="col-xs-12 field-input">
<select ng-model="user.language" required>
<option ng-repeat="language in languages"
ng-selected="language == user.language"
value="{{language}}">
{{language}}
</option>
</select>
</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="user.language" search-enabled="false" theme="selectize">
<ui-select-match>
{{ $root.langCodeToWord[$select.selected] }}
</ui-select-match>
<ui-select-choices repeat="language in $root.languages">
<span ng-bind-html="$root.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 form-group">
<div class="col-xs-7 field-title">
{{ 'USERNAME_LABEL' | translate }}
<h4>{{ 'USERNAME_LABEL' | translate }}</h4>
</div>
<div class="col-xs-12 field-input">
<input type="text" id="username" name="username" class="form-control" data-ng-model="user.username">
@ -58,7 +62,7 @@
<div class="row form-group">
<div class="col-xs-7 field-title">
{{ 'EMAIL_LABEL' | translate }}
<h4>{{ 'EMAIL_LABEL' | translate }}</h4>
</div>
<div class="col-xs-12 field-input">
<input type="email" id="email" name="email" class="form-control" data-ng-model="user.email">

View file

@ -1,31 +0,0 @@
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
<section class="row" data-ng-controller="SettingsController">
<h3 class="col-md-12 text-center" data-ng-show="hasConnectedAdditionalSocialAccounts()">{{ 'CONNECTED_SOCIAL_ACCOUNTS' | translate }}:</h3>
<div class="col-md-12 text-center">
<div data-ng-repeat="(providerName, providerData) in user.additionalProvidersData" class="remove-account-container">
<img ng-src="/modules/users/img/buttons/{{providerName}}.png">
<a class="btn btn-danger btn-remove-account" data-ng-click="removeUserSocialAccount(providerName)">
<i class="glyphicon glyphicon-trash"></i>
</a>
</div>
</div>
<h3 class="col-md-12 text-center">{{ 'CONNECT_OTHER_SOCIAL_ACCOUNTS' | translate }}</h3>
<div class="col-md-12 text-center">
<a href="/auth/facebook" data-ng-hide="isConnectedSocialAccount('facebook')" class="undecorated-link">
<img src="/modules/users/img/buttons/facebook.png">
</a>
<a href="/auth/twitter" data-ng-hide="isConnectedSocialAccount('twitter')" class="undecorated-link">
<img src="/modules/users/img/buttons/twitter.png">
</a>
<a href="/auth/google" data-ng-hide="isConnectedSocialAccount('google')" class="undecorated-link">
<img src="/modules/users/img/buttons/google.png">
</a>
<a href="/auth/linkedin" data-ng-hide="isConnectedSocialAccount('linkedin')" class="undecorated-link">
<img src="/modules/users/img/buttons/linkedin.png">
</a>
<a href="/auth/github" data-ng-hide="isConnectedSocialAccount('github')" class="undecorated-link">
<img src="/modules/users/img/buttons/github.png">
</a>
</div>
</section>

View file

@ -811,7 +811,7 @@
}
}
},
"hideFooter": {
"showFooter": {
"type": "boolean",
"default": false,
"description": "Specifies whether to hide or show Form Footer",