got setup script to work
This commit is contained in:
parent
76c5520541
commit
883a944c83
14
a.txt
Normal file
14
a.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
APP_NAME=
|
||||||
|
APP_DESC=
|
||||||
|
APP_KEYWORDS=
|
||||||
|
SIGNUP_DISABLED=false
|
||||||
|
NODE_ENV=dev
|
||||||
|
MAILER_SERVICE_PROVIDER=1und1
|
||||||
|
MAILER_EMAIL_ID=
|
||||||
|
MAILER_PASSWORD=
|
||||||
|
MAILER_FROM=
|
||||||
|
BASE_URL=127.0.0.1
|
||||||
|
PORT=3000
|
||||||
|
GOOGLE_ANALYTICS_ID=
|
||||||
|
email=
|
||||||
|
password=
|
|
@ -80,7 +80,8 @@ var UserSchema = new Schema({
|
||||||
},
|
},
|
||||||
provider: {
|
provider: {
|
||||||
type: String,
|
type: String,
|
||||||
required: 'Provider is required'
|
required: 'Provider is required',
|
||||||
|
default: 'local'
|
||||||
},
|
},
|
||||||
providerData: {},
|
providerData: {},
|
||||||
additionalProvidersData: {},
|
additionalProvidersData: {},
|
||||||
|
|
10
config/env/all.js
vendored
10
config/env/all.js
vendored
|
@ -3,9 +3,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app: {
|
app: {
|
||||||
google_analytics_id: process.env.GOOGLE_ANALYTICS_ID || '',
|
google_analytics_id: process.env.GOOGLE_ANALYTICS_ID || '',
|
||||||
title: 'TellForm',
|
title: process.env.APP_NAME || 'TellForm',
|
||||||
description: 'Opensource form builder alternative to TypeForm',
|
description: process.env.APP_DESC || 'Opensource form builder alternative to TypeForm',
|
||||||
keywords: 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs',
|
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||||
},
|
},
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
templateEngine: 'swig',
|
templateEngine: 'swig',
|
||||||
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
|
|
||||||
mailosaur: {
|
mailosaur: {
|
||||||
key: process.env.MAILOSAUR_KEY || '',
|
key: process.env.MAILOSAUR_KEY || '',
|
||||||
mailbox_id: process.env.MAILOSAUR_MAILBOX || '',
|
mailbox_id: process.env.MAILOSAUR_MAILBOX || ''
|
||||||
},
|
},
|
||||||
|
|
||||||
//Sentry DSN Client Key
|
//Sentry DSN Client Key
|
||||||
|
@ -38,7 +38,7 @@ module.exports = {
|
||||||
secure: false,
|
secure: false,
|
||||||
// Only set the maxAge to null if the cookie shouldn't be expired
|
// Only set the maxAge to null if the cookie shouldn't be expired
|
||||||
// at all. The cookie will expunge when the browser is closed.
|
// at all. The cookie will expunge when the browser is closed.
|
||||||
maxAge: null,
|
maxAge: null
|
||||||
// To set the cookie in a specific domain uncomment the following
|
// To set the cookie in a specific domain uncomment the following
|
||||||
// setting:
|
// setting:
|
||||||
// domain: 'tellform.com'
|
// domain: 'tellform.com'
|
||||||
|
|
3
config/env/development.js
vendored
3
config/env/development.js
vendored
|
@ -18,9 +18,6 @@ module.exports = {
|
||||||
// stream: 'access.log'
|
// stream: 'access.log'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
app: {
|
|
||||||
title: 'TellForm'
|
|
||||||
},
|
|
||||||
sessionCookie: {
|
sessionCookie: {
|
||||||
domain: process.env.BASE_URL || 'http://localhost:3000'
|
domain: process.env.BASE_URL || 'http://localhost:3000'
|
||||||
},
|
},
|
||||||
|
|
191
gruntfile.js
191
gruntfile.js
|
@ -1,4 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
require('jit-grunt')(grunt);
|
require('jit-grunt')(grunt);
|
||||||
|
@ -69,81 +71,81 @@ module.exports = function(grunt) {
|
||||||
options: {
|
options: {
|
||||||
jshintrc: true
|
jshintrc: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allTests: {
|
allTests: {
|
||||||
src: watchFiles.allTests,
|
src: watchFiles.allTests,
|
||||||
options: {
|
options: {
|
||||||
jshintrc: true
|
jshintrc: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
csslint: {
|
csslint: {
|
||||||
options: {
|
options: {
|
||||||
csslintrc: '.csslintrc'
|
csslintrc: '.csslintrc'
|
||||||
},
|
},
|
||||||
all: {
|
all: {
|
||||||
src: watchFiles.clientCSS
|
src: watchFiles.clientCSS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
uglify: {
|
uglify: {
|
||||||
production: {
|
production: {
|
||||||
options: {
|
options: {
|
||||||
mangle: false
|
mangle: false
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
'public/dist/application.min.js': 'public/dist/application.js'
|
'public/dist/application.min.js': 'public/dist/application.js'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cssmin: {
|
cssmin: {
|
||||||
combine: {
|
combine: {
|
||||||
files: {
|
files: {
|
||||||
'public/dist/application.min.css': '<%= applicationCSSFiles %>'
|
'public/dist/application.min.css': '<%= applicationCSSFiles %>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nodemon: {
|
nodemon: {
|
||||||
dev: {
|
dev: {
|
||||||
script: 'server.js',
|
script: 'server.js',
|
||||||
options: {
|
options: {
|
||||||
nodeArgs: ['--debug'],
|
nodeArgs: ['--debug'],
|
||||||
ext: 'js,html',
|
ext: 'js,html',
|
||||||
watch: watchFiles.serverViews.concat(watchFiles.serverJS)
|
watch: watchFiles.serverViews.concat(watchFiles.serverJS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'node-inspector': {
|
'node-inspector': {
|
||||||
custom: {
|
custom: {
|
||||||
options: {
|
options: {
|
||||||
'web-port': 1337,
|
'web-port': 1337,
|
||||||
'web-host': 'localhost',
|
'web-host': 'localhost',
|
||||||
'debug-port': 5858,
|
'debug-port': 5858,
|
||||||
'save-live-edit': true,
|
'save-live-edit': true,
|
||||||
'no-preload': true,
|
'no-preload': true,
|
||||||
'stack-trace-limit': 50,
|
'stack-trace-limit': 50,
|
||||||
'hidden': []
|
'hidden': []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ngAnnotate: {
|
ngAnnotate: {
|
||||||
production: {
|
production: {
|
||||||
files: {
|
files: {
|
||||||
'public/dist/application.js': '<%= applicationJavaScriptFiles %>'
|
'public/dist/application.js': '<%= applicationJavaScriptFiles %>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
concurrent: {
|
concurrent: {
|
||||||
default: ['nodemon', 'watch'],
|
default: ['nodemon', 'watch'],
|
||||||
debug: ['nodemon', 'watch', 'node-inspector'],
|
debug: ['nodemon', 'watch', 'node-inspector'],
|
||||||
options: {
|
options: {
|
||||||
logConcurrentOutput: true,
|
logConcurrentOutput: true,
|
||||||
limit: 10
|
limit: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
test: {
|
test: {
|
||||||
NODE_ENV: 'test',
|
NODE_ENV: 'test',
|
||||||
src: '.env'
|
src: '.env'
|
||||||
},
|
},
|
||||||
secure: {
|
secure: {
|
||||||
NODE_ENV: 'secure',
|
NODE_ENV: 'secure',
|
||||||
|
@ -156,7 +158,7 @@ NODE_ENV: 'test',
|
||||||
dev: {
|
dev: {
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'development',
|
||||||
src: '.env'
|
src: '.env'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
mochaTest: {
|
mochaTest: {
|
||||||
src: watchFiles.serverTests,
|
src: watchFiles.serverTests,
|
||||||
|
@ -230,26 +232,31 @@ NODE_ENV: 'test',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
html2js: {
|
html2js: {
|
||||||
options: {
|
options: {
|
||||||
base: 'NodeForm',
|
base: 'NodeForm',
|
||||||
watch: true,
|
watch: true,
|
||||||
module: 'NodeForm.templates',
|
module: 'NodeForm.templates',
|
||||||
singleModule: true,
|
singleModule: true,
|
||||||
useStrict: true,
|
useStrict: true,
|
||||||
htmlmin: {
|
htmlmin: {
|
||||||
collapseBooleanAttributes: true,
|
collapseBooleanAttributes: true,
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
removeAttributeQuotes: true,
|
removeAttributeQuotes: true,
|
||||||
removeComments: true,
|
removeComments: true,
|
||||||
removeEmptyAttributes: true,
|
removeEmptyAttributes: true,
|
||||||
removeRedundantAttributes: true
|
removeRedundantAttributes: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html'],
|
src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html'],
|
||||||
dest: 'public/dist/populate_template_cache.js'
|
dest: 'public/dist/populate_template_cache.js'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
execute: {
|
||||||
|
target: {
|
||||||
|
src: ['./scripts/setup.js']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -278,7 +285,6 @@ NODE_ENV: 'test',
|
||||||
grunt.config.set('applicationCSSFiles', config.assets.css);
|
grunt.config.set('applicationCSSFiles', config.assets.css);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Code coverage tasks.
|
// Code coverage tasks.
|
||||||
grunt.registerTask('coveralls', ['env:test','mocha_istanbul:coveralls']);
|
grunt.registerTask('coveralls', ['env:test','mocha_istanbul:coveralls']);
|
||||||
grunt.registerTask('coverage', ['env:test', 'mocha_istanbul:coverage']);
|
grunt.registerTask('coverage', ['env:test', 'mocha_istanbul:coverage']);
|
||||||
|
@ -288,9 +294,11 @@ NODE_ENV: 'test',
|
||||||
// Default task(s).
|
// Default task(s).
|
||||||
grunt.registerTask('default', ['lint', 'html2js:main', 'env', 'concurrent:default']);
|
grunt.registerTask('default', ['lint', 'html2js:main', 'env', 'concurrent:default']);
|
||||||
grunt.registerTask('dev', ['lint', 'html2js:main', 'env:dev', 'concurrent:default']);
|
grunt.registerTask('dev', ['lint', 'html2js:main', 'env:dev', 'concurrent:default']);
|
||||||
|
|
||||||
// Debug task.
|
// Debug task.
|
||||||
grunt.registerTask('debug', ['lint', 'html2js:main', 'concurrent:debug']);
|
grunt.registerTask('debug', ['lint', 'html2js:main', 'concurrent:debug']);
|
||||||
|
|
||||||
|
|
||||||
// Secure task(s).
|
// Secure task(s).
|
||||||
grunt.registerTask('secure', ['env:secure', 'lint', 'html2js:main', 'concurrent:default']);
|
grunt.registerTask('secure', ['env:secure', 'lint', 'html2js:main', 'concurrent:default']);
|
||||||
|
|
||||||
|
@ -301,6 +309,9 @@ NODE_ENV: 'test',
|
||||||
// Build task(s).
|
// Build task(s).
|
||||||
grunt.registerTask('build', ['lint', 'loadConfig', 'cssmin', 'ngAnnotate', 'uglify', 'html2js:main']);
|
grunt.registerTask('build', ['lint', 'loadConfig', 'cssmin', 'ngAnnotate', 'uglify', 'html2js:main']);
|
||||||
|
|
||||||
|
//Setup task(s).
|
||||||
|
grunt.registerTask('setup', ['execute']);
|
||||||
|
|
||||||
// Test task(s).
|
// Test task(s).
|
||||||
grunt.registerTask('test', ['lint:tests', 'test:server', 'test:client']);
|
grunt.registerTask('test', ['lint:tests', 'test:server', 'test:client']);
|
||||||
grunt.registerTask('test:server', ['lint:tests', 'env:test', 'mochaTest']);
|
grunt.registerTask('test:server', ['lint:tests', 'env:test', 'mochaTest']);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,13 +25,14 @@
|
||||||
"async": "^1.4.2",
|
"async": "^1.4.2",
|
||||||
"body-parser": "~1.14.1",
|
"body-parser": "~1.14.1",
|
||||||
"bower": "~1.6.5",
|
"bower": "~1.6.5",
|
||||||
"chalk": "~1.1.1",
|
"chalk": "^1.1.3",
|
||||||
"compression": "~1.6.0",
|
"compression": "~1.6.0",
|
||||||
"connect-flash": "~0.1.1",
|
"connect-flash": "~0.1.1",
|
||||||
"connect-mongo": "~0.8.2",
|
"connect-mongo": "~0.8.2",
|
||||||
"consolidate": "~0.13.1",
|
"consolidate": "~0.13.1",
|
||||||
"cookie-parser": "~1.4.0",
|
"cookie-parser": "~1.4.0",
|
||||||
"email-verification": "whitef0x0/node-email-verification",
|
"email-verification": "whitef0x0/node-email-verification",
|
||||||
|
"envfile": "^2.0.1",
|
||||||
"express": "~4.13.3",
|
"express": "~4.13.3",
|
||||||
"express-device": "~0.4.2",
|
"express-device": "~0.4.2",
|
||||||
"express-session": "~1.12.1",
|
"express-session": "~1.12.1",
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
"grunt-node-inspector": "~0.4.1",
|
"grunt-node-inspector": "~0.4.1",
|
||||||
"grunt-nodemon": "~0.4.0",
|
"grunt-nodemon": "~0.4.0",
|
||||||
"helmet": "~0.14.0",
|
"helmet": "~0.14.0",
|
||||||
|
"inquirer": "^1.0.2",
|
||||||
"jit-grunt": "^0.9.1",
|
"jit-grunt": "^0.9.1",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^2.4.1",
|
||||||
"main-bower-files": "~2.9.0",
|
"main-bower-files": "~2.9.0",
|
||||||
|
@ -83,6 +85,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"coveralls": "^2.11.4",
|
"coveralls": "^2.11.4",
|
||||||
"glob": "^7.0.3",
|
"glob": "^7.0.3",
|
||||||
|
"grunt-execute": "^0.2.2",
|
||||||
"grunt-mocha-istanbul": "^3.0.1",
|
"grunt-mocha-istanbul": "^3.0.1",
|
||||||
"grunt-mocha-test": "~0.12.1",
|
"grunt-mocha-test": "~0.12.1",
|
||||||
"istanbul": "^0.4.0",
|
"istanbul": "^0.4.0",
|
||||||
|
|
101
public/dist/application.js
vendored
101
public/dist/application.js
vendored
|
@ -522,10 +522,10 @@ angular.module('core').service('Menus', [
|
||||||
// Define the menus object
|
// Define the menus object
|
||||||
this.menus = {};
|
this.menus = {};
|
||||||
|
|
||||||
// A private function for rendering decision
|
// A private function for rendering decision
|
||||||
var shouldRender = function(user) {
|
var shouldRender = function(user) {
|
||||||
if (user) {
|
if (user) {
|
||||||
if (!!~this.roles.indexOf('*')) {
|
if (~this.roles.indexOf('*')) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
for (var userRoleIndex in user.roles) {
|
for (var userRoleIndex in user.roles) {
|
||||||
|
@ -681,6 +681,7 @@ angular.module('core').service('Menus', [
|
||||||
this.addMenu('bottombar', false, ['*']);
|
this.addMenu('bottombar', false, ['*']);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Configuring the Forms drop-down menus
|
// Configuring the Forms drop-down menus
|
||||||
|
@ -797,15 +798,15 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: 'Design',
|
heading: 'Design',
|
||||||
route: 'viewForm.design',
|
route: 'viewForm.design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: 'Configure',
|
heading: 'Configure',
|
||||||
route: 'viewForm.configure',
|
route: 'viewForm.configure'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: 'Analyze',
|
heading: 'Analyze',
|
||||||
route: 'viewForm.analyze',
|
route: 'viewForm.analyze'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -818,8 +819,8 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** DeleteModal Functions
|
** DeleteModal Functions
|
||||||
*/
|
*/
|
||||||
$scope.openDeleteModal = function(){
|
$scope.openDeleteModal = function(){
|
||||||
$scope.deleteModal = $uibModal.open({
|
$scope.deleteModal = $uibModal.open({
|
||||||
|
@ -851,15 +852,15 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
if($scope.deleteModal && $scope.deleteModal.opened){
|
if($scope.deleteModal && $scope.deleteModal.opened){
|
||||||
|
|
||||||
$scope.deleteModal.close();
|
$scope.deleteModal.close();
|
||||||
|
|
||||||
var form_id = $scope.myform._id;
|
var form_id = $scope.myform._id;
|
||||||
if(!form_id) throw new Error('Error - removeCurrentForm(): $scope.myform._id does not exist');
|
if(!form_id) throw new Error('Error - removeCurrentForm(): $scope.myform._id does not exist');
|
||||||
|
|
||||||
$http.delete('/forms/'+form_id)
|
$http.delete('/forms/'+form_id)
|
||||||
.success(function(data, status, headers){
|
.success(function(data, status, headers){
|
||||||
console.log('form deleted successfully');
|
console.log('form deleted successfully');
|
||||||
|
|
||||||
$state.go('listForms', {}, {reload: true});
|
$state.go('listForms', {}, {reload: true});
|
||||||
|
|
||||||
}).error(function(error){
|
}).error(function(error){
|
||||||
console.log('ERROR: Form could not be deleted.');
|
console.log('ERROR: Form could not be deleted.');
|
||||||
|
@ -875,7 +876,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
if(!updateImmediately){
|
if(!updateImmediately){
|
||||||
continueUpdate = !$rootScope.saveInProgress;
|
continueUpdate = !$rootScope.saveInProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update form **if we are not currently updating** or if **shouldUpdateNow flag is set**
|
//Update form **if we are not currently updating** or if **shouldUpdateNow flag is set**
|
||||||
if(continueUpdate){
|
if(continueUpdate){
|
||||||
var err = null;
|
var err = null;
|
||||||
|
@ -890,12 +891,12 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
console.log('Error occured during form UPDATE.\n');
|
console.log('Error occured during form UPDATE.\n');
|
||||||
// console.log(response.data);
|
// console.log(response.data);
|
||||||
err = response.data;
|
err = response.data;
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
// console.log('finished updating');
|
// console.log('finished updating');
|
||||||
if(!updateImmediately){$rootScope.saveInProgress = false; }
|
if(!updateImmediately){$rootScope.saveInProgress = false; }
|
||||||
|
|
||||||
if( (typeof cb) === 'function'){
|
if( (typeof cb) === 'function'){
|
||||||
cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -904,6 +905,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||||
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Forms controller
|
// Forms controller
|
||||||
|
@ -1237,7 +1239,7 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$htt
|
||||||
console.log('Error occured during upload.\n');
|
console.log('Error occured during upload.\n');
|
||||||
console.log(resp.status);
|
console.log(resp.status);
|
||||||
}, function (evt) {
|
}, function (evt) {
|
||||||
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
|
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
|
||||||
$scope.log = 'progress: ' + progressPercentage + '% ' +
|
$scope.log = 'progress: ' + progressPercentage + '% ' +
|
||||||
evt.config.data.file.name + '\n' + $scope.log;
|
evt.config.data.file.name + '\n' + $scope.log;
|
||||||
|
|
||||||
|
@ -1261,7 +1263,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
templateUrl: 'modules/forms/views/directiveViews/form/edit-form.client.view.html',
|
templateUrl: 'modules/forms/views/directiveViews/form/edit-form.client.view.html',
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
scope: {
|
scope: {
|
||||||
myform:'=',
|
myform:'='
|
||||||
},
|
},
|
||||||
controller: ["$scope", function($scope){
|
controller: ["$scope", function($scope){
|
||||||
var field_ids = _($scope.myform.form_fields).pluck('_id');
|
var field_ids = _($scope.myform.form_fields).pluck('_id');
|
||||||
|
@ -1300,7 +1302,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
|
|
||||||
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){
|
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){
|
||||||
currentFields = _(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id]);
|
currentFields = _(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get all oscarhostFields that haven't been mapped to a formfield
|
//Get all oscarhostFields that haven't been mapped to a formfield
|
||||||
return _(oscarhostFields).difference(currentFields).value();
|
return _(oscarhostFields).difference(currentFields).value();
|
||||||
|
@ -1314,7 +1316,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
$scope.dropzone = {
|
$scope.dropzone = {
|
||||||
handle: ' .handle',
|
handle: ' .handle',
|
||||||
containment: '.dropzoneContainer',
|
containment: '.dropzoneContainer',
|
||||||
cursor: 'grabbing',
|
cursor: 'grabbing'
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1328,9 +1330,9 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
var fieldTitle;
|
var fieldTitle;
|
||||||
|
|
||||||
for(var i = 0; i < $scope.addField.types.length; i++){
|
for(var i = 0; i < $scope.addField.types.length; i++){
|
||||||
if($scope.addField.types[i].name === fieldType){
|
if($scope.addField.types[i].name === fieldType){
|
||||||
$scope.addField.types[i].lastAddedID++;
|
$scope.addField.types[i].lastAddedID++;
|
||||||
fieldTitle = $scope.addField.types[i].value+$scope.addField.types[i].lastAddedID;
|
fieldTitle = $scope.addField.types[i].value+$scope.addField.types[i].lastAddedID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1345,12 +1347,12 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
// console.log('\n\n---------\nAdded field CLIENT');
|
// console.log('\n\n---------\nAdded field CLIENT');
|
||||||
// console.log(newField);
|
// console.log(newField);
|
||||||
// newField._id = _.uniqueId();
|
// newField._id = _.uniqueId();
|
||||||
|
|
||||||
// put newField into fields array
|
// put newField into fields array
|
||||||
if(modifyForm){
|
if(modifyForm){
|
||||||
$scope.myform.form_fields.push(newField);
|
$scope.myform.form_fields.push(newField);
|
||||||
}
|
}
|
||||||
return newField;
|
return newField;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete particular field on button click
|
// Delete particular field on button click
|
||||||
|
@ -1364,7 +1366,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
$scope.myform.form_fields.splice(field_index, 1);
|
$scope.myform.form_fields.splice(field_index, 1);
|
||||||
};
|
};
|
||||||
$scope.duplicateField = function (field_index){
|
$scope.duplicateField = function (field_index){
|
||||||
var currField = _.cloneDeep($scope.myform.form_fields[field_index]);
|
var currField = _.cloneDeep($scope.myform.form_fields[field_index]);
|
||||||
currField._id = 'cloned'+_.uniqueId();
|
currField._id = 'cloned'+_.uniqueId();
|
||||||
currField.title += ' copy';
|
currField.title += ' copy';
|
||||||
|
|
||||||
|
@ -1413,8 +1415,8 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
$scope.addOption = function(field_index){
|
$scope.addOption = function(field_index){
|
||||||
var currField = $scope.myform.form_fields[field_index];
|
var currField = $scope.myform.form_fields[field_index];
|
||||||
console.log(field_index);
|
console.log(field_index);
|
||||||
console.log(currField);
|
console.log(currField);
|
||||||
|
|
||||||
if(currField.fieldType === 'checkbox' || currField.fieldType === 'dropdown' || currField.fieldType === 'radio'){
|
if(currField.fieldType === 'checkbox' || currField.fieldType === 'dropdown' || currField.fieldType === 'radio'){
|
||||||
if(!currField.fieldOptions) $scope.myform.form_fields[field_index].fieldOptions = [];
|
if(!currField.fieldOptions) $scope.myform.form_fields[field_index].fieldOptions = [];
|
||||||
|
|
||||||
|
@ -1430,7 +1432,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
var newOption = {
|
var newOption = {
|
||||||
'option_id' : Math.floor(100000*Math.random()),
|
'option_id' : Math.floor(100000*Math.random()),
|
||||||
'option_title' : 'Option '+lastOptionID,
|
'option_title' : 'Option '+lastOptionID,
|
||||||
'option_value' : 'Option ' +lastOptionID,
|
'option_value' : 'Option ' +lastOptionID
|
||||||
};
|
};
|
||||||
|
|
||||||
// put new option into fieldOptions array
|
// put new option into fieldOptions array
|
||||||
|
@ -1463,8 +1465,8 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}],
|
}]
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -1520,7 +1522,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
defaultFormFields = _.cloneDeep($scope.myform.form_fields);
|
defaultFormFields = _.cloneDeep($scope.myform.form_fields);
|
||||||
|
|
||||||
// console.log('before textField2: '+data[0].form_fields[1].fieldValue);
|
// console.log('before textField2: '+data[0].form_fields[1].fieldValue);
|
||||||
|
|
||||||
//Iterate through form's submissions
|
//Iterate through form's submissions
|
||||||
for(var i=0; i<data.length; i++){
|
for(var i=0; i<data.length; i++){
|
||||||
for(var x=0; x<data[i].form_fields; x++){
|
for(var x=0; x<data[i].form_fields; x++){
|
||||||
|
@ -1540,8 +1542,8 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
})
|
})
|
||||||
.error(function(err){
|
.error(function(err){
|
||||||
console.error('Could not fetch form submissions.\nError: '+err);
|
console.error('Could not fetch form submissions.\nError: '+err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//Delete selected submissions of Form
|
//Delete selected submissions of Form
|
||||||
$scope.deleteSelectedSubmissions = function(){
|
$scope.deleteSelectedSubmissions = function(){
|
||||||
|
@ -1550,7 +1552,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
return !!row.selected;
|
return !!row.selected;
|
||||||
}).pluck('_id').value();
|
}).pluck('_id').value();
|
||||||
|
|
||||||
$http({ url: '/forms/'+$scope.myform._id+'/submissions',
|
$http({ url: '/forms/'+$scope.myform._id+'/submissions',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
data: {deleted_submissions: delete_ids},
|
data: {deleted_submissions: delete_ids},
|
||||||
headers: {'Content-Type': 'application/json;charset=utf-8'}
|
headers: {'Content-Type': 'application/json;charset=utf-8'}
|
||||||
|
@ -1568,7 +1570,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
console.log('Could not delete form submissions.\nError: ');
|
console.log('Could not delete form submissions.\nError: ');
|
||||||
console.log(err);
|
console.log(err);
|
||||||
console.error = err;
|
console.error = err;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//Export selected submissions of Form
|
//Export selected submissions of Form
|
||||||
|
@ -1576,7 +1578,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||||
var fileMIMETypeMap = {
|
var fileMIMETypeMap = {
|
||||||
'xls': 'vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
'xls': 'vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
'json': 'json',
|
'json': 'json',
|
||||||
'csv': 'csv',
|
'csv': 'csv'
|
||||||
};
|
};
|
||||||
|
|
||||||
var blob = new Blob([document.getElementById('table-submission-data').innerHTM], {
|
var blob = new Blob([document.getElementById('table-submission-data').innerHTM], {
|
||||||
|
@ -1657,7 +1659,6 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
|
||||||
if (__indexOf.call(supported_fields, type) >= 0) {
|
if (__indexOf.call(supported_fields, type) >= 0) {
|
||||||
templateUrl = templateUrl+type+'.html';
|
templateUrl = templateUrl+type+'.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $templateCache.get('../public/'+templateUrl);
|
return $templateCache.get('../public/'+templateUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1828,28 +1829,30 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
$scope.fieldBottom = elemBox.bottom;
|
$scope.fieldBottom = elemBox.bottom;
|
||||||
|
|
||||||
//console.log($scope.forms.myForm);
|
//console.log($scope.forms.myForm);
|
||||||
|
var field_id;
|
||||||
|
var field_index;
|
||||||
|
|
||||||
if(!$scope.noscroll){
|
if(!$scope.noscroll){
|
||||||
//Focus on submit button
|
//Focus on submit button
|
||||||
if( $scope.selected.index === $scope.myform.form_fields.length-1 && $scope.fieldBottom < 200){
|
if( $scope.selected.index === $scope.myform.form_fields.length-1 && $scope.fieldBottom < 200){
|
||||||
var field_index = $scope.selected.index+1;
|
field_index = $scope.selected.index+1;
|
||||||
var field_id = 'submit_field';
|
field_id = 'submit_field';
|
||||||
$scope.setActiveField(field_id, field_index, false);
|
$scope.setActiveField(field_id, field_index, false);
|
||||||
}
|
}
|
||||||
//Focus on field above submit button
|
//Focus on field above submit button
|
||||||
else if($scope.selected.index === $scope.myform.form_fields.length){
|
else if($scope.selected.index === $scope.myform.form_fields.length){
|
||||||
if($scope.fieldTop > 200){
|
if($scope.fieldTop > 200){
|
||||||
var field_index = $scope.selected.index-1;
|
field_index = $scope.selected.index-1;
|
||||||
var field_id = $scope.myform.form_fields[field_index]._id;
|
field_id = $scope.myform.form_fields[field_index]._id;
|
||||||
$scope.setActiveField(field_id, field_index, false);
|
$scope.setActiveField(field_id, field_index, false);
|
||||||
}
|
}
|
||||||
}else if( $scope.fieldBottom < 0){
|
}else if( $scope.fieldBottom < 0){
|
||||||
var field_index = $scope.selected.index+1;
|
field_index = $scope.selected.index+1;
|
||||||
var field_id = $scope.myform.form_fields[field_index]._id;
|
field_id = $scope.myform.form_fields[field_index]._id;
|
||||||
$scope.setActiveField(field_id, field_index, false);
|
$scope.setActiveField(field_id, field_index, false);
|
||||||
}else if ( $scope.selected.index !== 0 && $scope.fieldTop > 0) {
|
}else if ( $scope.selected.index !== 0 && $scope.fieldTop > 0) {
|
||||||
var field_index = $scope.selected.index-1;
|
field_index = $scope.selected.index-1;
|
||||||
var field_id = $scope.myform.form_fields[field_index]._id;
|
field_id = $scope.myform.form_fields[field_index]._id;
|
||||||
$scope.setActiveField(field_id, field_index, false);
|
$scope.setActiveField(field_id, field_index, false);
|
||||||
}
|
}
|
||||||
//console.log('$scope.selected.index: '+$scope.selected.index);
|
//console.log('$scope.selected.index: '+$scope.selected.index);
|
||||||
|
@ -2546,7 +2549,7 @@ angular.module('users').factory('Auth', ['$window',
|
||||||
$window.user = null;
|
$window.user = null;
|
||||||
userState.isLoggedIn = false;
|
userState.isLoggedIn = false;
|
||||||
service._currentUser = null;
|
service._currentUser = null;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
|
@ -2617,7 +2620,7 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
logout: function() {
|
logout: function() {
|
||||||
|
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
$http.get('/auth/signout').success(function(response) {
|
$http.get('/auth/signout').success(function(response) {
|
||||||
|
@ -2628,7 +2631,7 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
signup: function(credentials) {
|
signup: function(credentials) {
|
||||||
|
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
$http.post('/auth/signup', credentials).success(function(response) {
|
$http.post('/auth/signup', credentials).success(function(response) {
|
||||||
|
@ -2641,7 +2644,7 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
resendVerifyEmail: function(_email) {
|
resendVerifyEmail: function(_email) {
|
||||||
|
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
$http.post('/auth/verify', {email: _email}).success(function(response) {
|
$http.post('/auth/verify', {email: _email}).success(function(response) {
|
||||||
|
@ -2653,7 +2656,7 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
validateVerifyToken: function(token) {
|
validateVerifyToken: function(token) {
|
||||||
|
|
||||||
//DAVID: TODO: The valid length of a token should somehow be linked to server config values
|
//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?
|
//DAVID: TODO: SEMI-URGENT: Should we even be doing this?
|
||||||
|
@ -2695,12 +2698,12 @@ angular.module('users').factory('User', ['$window', '$q', '$timeout', '$http', '
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return userService;
|
return userService;
|
||||||
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
4
public/dist/application.min.js
vendored
4
public/dist/application.min.js
vendored
File diff suppressed because one or more lines are too long
201
scripts/setup.js
Normal file
201
scripts/setup.js
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
|
||||||
|
var init = require('../config/init')(),
|
||||||
|
config = require('../config/config'),
|
||||||
|
mongoose = require('mongoose'),
|
||||||
|
inquirer = require('inquirer'),
|
||||||
|
envfile = require('envfile'),
|
||||||
|
fs = require('fs-extra'),
|
||||||
|
chalk = require('chalk');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application entry file.
|
||||||
|
* Please note that the order of loading is important.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Bootstrap db connection
|
||||||
|
var db = mongoose.connect(config.db.uri, config.db.options, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error(chalk.red('Could not connect to MongoDB!'));
|
||||||
|
console.log(chalk.red(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mongoose.connection.on('error', function(err) {
|
||||||
|
console.error(chalk.red('MongoDB connection error: ' + err));
|
||||||
|
process.exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init the express application
|
||||||
|
var app = require('../config/express')(db);
|
||||||
|
|
||||||
|
// Bootstrap passport config
|
||||||
|
require('../config/passport')();
|
||||||
|
|
||||||
|
var User = mongoose.model('User');
|
||||||
|
require('../app/models/user.server.model.js');
|
||||||
|
|
||||||
|
var nodemailer_providers = [
|
||||||
|
'1und1',
|
||||||
|
'AOL',
|
||||||
|
'DebugMail.io',
|
||||||
|
'DynectEmail',
|
||||||
|
'FastMail',
|
||||||
|
'GandiMail',
|
||||||
|
'Gmail',
|
||||||
|
'Godaddy',
|
||||||
|
'GodaddyAsia',
|
||||||
|
'GodaddyEurope',
|
||||||
|
'hot.ee',
|
||||||
|
'Hotmail',
|
||||||
|
'iCloud',
|
||||||
|
'mail.ee',
|
||||||
|
'Mail.ru',
|
||||||
|
'Mailgun',
|
||||||
|
'Mailjet',
|
||||||
|
'Mandrill',
|
||||||
|
'Naver',
|
||||||
|
'OpenMailBox',
|
||||||
|
'Postmark',
|
||||||
|
'QQ',
|
||||||
|
'QQex',
|
||||||
|
'SendCloud',
|
||||||
|
'SendGrid',
|
||||||
|
'SES',
|
||||||
|
'SES-US-EAST-1',
|
||||||
|
'SES-US-WEST-1',
|
||||||
|
'SES-EU-WEST-1',
|
||||||
|
'Sparkpost',
|
||||||
|
'Yahoo',
|
||||||
|
'Yandex',
|
||||||
|
'Zoho'
|
||||||
|
];
|
||||||
|
|
||||||
|
var questions = [
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'shouldContinue',
|
||||||
|
message: 'Do you wish to configure your deployment now?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'APP_NAME',
|
||||||
|
message: 'What do you want to name your TellForm deployment?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'APP_DESC',
|
||||||
|
message: 'Describe your project (for SEO)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'APP_KEYWORDS',
|
||||||
|
message: 'What keywords are relevant to your project (seperate by commas)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'SIGNUP_DISABLED',
|
||||||
|
message: 'Do you want to disable signups?',
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'NODE_ENV',
|
||||||
|
message: 'What should be the default environment',
|
||||||
|
choices: ['dev','production']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'MAILER_SERVICE_PROVIDER',
|
||||||
|
message: 'What email service provider are you using?',
|
||||||
|
choices: nodemailer_providers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'MAILER_EMAIL_ID',
|
||||||
|
message: 'What is your SMTP username?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'MAILER_PASSWORD',
|
||||||
|
message: 'What is your SMTP password?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'MAILER_FROM',
|
||||||
|
message: 'What do you want the default "from" email address to be?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'BASE_URL',
|
||||||
|
message: 'What is the url your TellForm will be hosted at?',
|
||||||
|
default: '127.0.0.1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'PORT',
|
||||||
|
message: 'What port should the TellForm server run on?',
|
||||||
|
default: '3000'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'GOOGLE_ANALYTICS_ID',
|
||||||
|
message: 'What is your Google Analytics Tag?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'email',
|
||||||
|
message: 'What should be the email for your admin account?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'password',
|
||||||
|
message: 'What should be the password for your admin account?'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(chalk.green('\n\nHi, welcome to TellForm Setup'));
|
||||||
|
|
||||||
|
console.log(chalk.green('This will only run the first time you run TellForm\n--------------------------------------------------\n\n'));
|
||||||
|
|
||||||
|
inquirer.prompt([questions[0]]).then(function (confirmAns) {
|
||||||
|
if(confirmAns['shouldContinue']) {
|
||||||
|
|
||||||
|
inquirer.prompt(questions.slice(1)).then(function (answers) {
|
||||||
|
|
||||||
|
var email = answers['email'];
|
||||||
|
var pass = answers['password'];
|
||||||
|
delete answers['email'];
|
||||||
|
delete answers['password'];
|
||||||
|
|
||||||
|
envfile.stringify(answers, function (err, str) {
|
||||||
|
fs.outputFile('..//.env', str, function(err){
|
||||||
|
if (err) return console.error(chalk.red(err));
|
||||||
|
console.log(chalk.green('Successfully created .env file'));
|
||||||
|
});
|
||||||
|
user = new User({
|
||||||
|
firstName: 'Admin',
|
||||||
|
lastName: 'Account',
|
||||||
|
email: email,
|
||||||
|
username: email,
|
||||||
|
password: pass,
|
||||||
|
provider: 'local',
|
||||||
|
roles: ['admin', 'user']
|
||||||
|
});
|
||||||
|
|
||||||
|
user.save(function (err) {
|
||||||
|
if (err) return console.error(chalk.red(err));
|
||||||
|
console.log(chalk.green('Succesfully created user'));
|
||||||
|
delete email;
|
||||||
|
delete pass;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(chalk.green('Have fun using TellForm!'));
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in a new issue