source upload
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Scope
|
||||
|
||||
Some part of an *AngularJS* client to a *mORMot* server.
|
||||
|
||||
It features login, with full authentication via a pure JavaScript / AngularJS client.
|
||||
|
||||
It is not a working demo, just some sources to get implementation ideas, and working code for authentication on a *mORMot* server.
|
||||
|
||||
To understand how it works is better to download Software (*G-Lock EasyMail*) as presented in topic http://synopse.info/forum/viewtopic.php?id=1954
|
||||
|
||||
# Forum Thread
|
||||
|
||||
See http://synopse.info/forum/viewtopic.php?pid=12547#p12547
|
||||
|
||||
|
||||
**enjoy!**
|
||||
|
||||
DigDiver
|
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="em7frontend">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<!-- Bootstrap -->
|
||||
<link href="bower_components/bootstrap/dist/css/bootstrap.css" rel="stylesheet" media="screen">
|
||||
<link href="css/signin.css" rel="stylesheet">
|
||||
|
||||
<script src="bower_components/jquery/jquery.min.js"></script>
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<!-- remove me --><script xrc="bower_components/angular/build/angular-route.js"></script>
|
||||
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
|
||||
<script src="bower_components/angular-bootstrap/ui-bootstrap.js"></script>
|
||||
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
|
||||
|
||||
<script src="js/lib/emmi.min.js"></script>
|
||||
<script src="js/lib/ckeditor/ckeditor.js"></script>
|
||||
<script src="js/lib/ng-infinite-scroll.js"></script>
|
||||
<script src="js/lib/multiselect/multiselect.js"></script>
|
||||
<script src="js/lib/long.js"></script>
|
||||
|
||||
<script src="js/lib/gt-select/gt-select.js"></script>
|
||||
<link href="js/lib/gt-select/gt-select.css" rel="stylesheet">
|
||||
|
||||
<script src="js/lib/gt-dropdown/gt-dropdown.js"></script>
|
||||
<link href="js/lib/gt-dropdown/gt-dropdown.css" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
<script src="js/lib/jquery.ui/jquery.ui.core.min.js"></script>
|
||||
<script src="js/lib/jquery.ui/jquery.ui.widget.min.js"></script>
|
||||
-->
|
||||
|
||||
<script src="js/lib/grf-uploader.js"></script>
|
||||
|
||||
<script src="js/em-utils.js"></script>
|
||||
<script src="js/syn-auth.js"></script>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/controllers/emails.js"></script>
|
||||
<script src="js/controllers/drafts.js"></script>
|
||||
<script src="js/controllers/templates.js"></script>
|
||||
<script src="js/controllers/editor.js"></script>
|
||||
<script src="js/controllers/contacts.js"></script>
|
||||
|
||||
</head>
|
||||
<body ng-controller="MainCtrl" class="{{section}}">
|
||||
<div emd-loadingbar></div>
|
||||
<div id="head">
|
||||
<div ng-show="fullUserName" class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" ng-click="toggleCollaped()" class="navbar-toggle">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">G-Lock EasyMail 7</a>
|
||||
</div>
|
||||
<div collapse="isCollapsed" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
|
||||
<li ng-hide="workplace"><a>{{fullUserName}}</a></li>
|
||||
|
||||
<li ng-show="workplace" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{fullUserName}} @ {{workplace.WPName}} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="wp in workplaces"><a href="#/workplaces/{{wp.ID}}/outbox">{{wp.WPName}}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#/logout">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li ng-if="!workplace"><a href="#/logout">Logout</a></li>
|
||||
|
||||
<!-- <span class="show-xxs-only-inline"> -->
|
||||
<li class="divider"></li>
|
||||
<li class="show-xs-only"><a ng-show="emlCanBeSent" ng-click="" href="#">Start sending</a></li>
|
||||
<li class="show-xs-only"><a ng-show="emlCanBeStopped" ng-click="" href="#">Stop</a></li>
|
||||
<li ng-show="item" class="divider"></li>
|
||||
<li ng-if="section.length && section !== 'contacts'" class="show-xs-only"><a href="#" ng-click="compose()">Compose</a></li>
|
||||
<li ng-if="section.length && section !== 'contacts'" ng-class="{ 'active':showPreview }" class="show-xs-only">
|
||||
<a ng-click="togglePreview()">Preview</a>
|
||||
</li>
|
||||
<li ng-if="section == 'contacts'" ng-class="{'active': showExclusionList}" class="show-xs-only">
|
||||
<a ng-click="toggleExclusionList()">Show Exclusion List</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ui-view="root"></div>
|
||||
<div id="foot" class="footer hide-xs-only">
|
||||
<div class="container">
|
||||
<p>© G-Lock Software</p>
|
||||
</div>
|
||||
</div></body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,476 @@
|
||||
function MainCtrl($rootScope, $scope, synConn, emAuth, $timeout, EmDrafts, $location, EmEml) {
|
||||
$scope.workplace = null;
|
||||
$scope.fullUserName = null;
|
||||
$scope.authenticated = false;
|
||||
|
||||
$scope.email = null;
|
||||
$scope.template = null;
|
||||
$scope.draft = null;
|
||||
|
||||
$scope.sub = null;
|
||||
|
||||
$scope.itemParams = null;
|
||||
|
||||
$scope.toggleCollaped = function(){
|
||||
$rootScope.isCollapsed = !$rootScope.isCollapsed;
|
||||
};
|
||||
|
||||
$scope.item = null;
|
||||
|
||||
$scope.setItem = function(item) {
|
||||
$scope.item = item;
|
||||
window.item = item;
|
||||
};
|
||||
|
||||
$scope.emlCanBeSent = false;
|
||||
$scope.emlCanBeStopped = false;
|
||||
|
||||
$scope.$watch('item.Status', function(itms){
|
||||
if ( itms ) {
|
||||
$scope.emlCanBeSent = EmEml.canBeSent(itms);
|
||||
$scope.emlCanBeStopped = EmEml.canBeStopped(itms);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.startSending = function() {
|
||||
if ( $scope.item ) {
|
||||
$scope.item.startSending();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.stopSending = function(){
|
||||
if ( $scope.item ) {
|
||||
$scope.item.stopSending();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.alerts = [];
|
||||
|
||||
$scope.alert = function(a) {
|
||||
$scope.alerts.push(a);
|
||||
var idx = $scope.alerts.length - 1;
|
||||
$timeout(function() {
|
||||
$scope.alerts.splice(idx, 1);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
if ( emAuth.get('userName') ) {
|
||||
$scope.fullUserName = emAuth.get('userName');
|
||||
}
|
||||
|
||||
$scope.setUserName = function(userName) {
|
||||
emAuth.set('userName', userName);
|
||||
$scope.fullUserName = userName;
|
||||
};
|
||||
|
||||
$scope.setWorkplace = function(wp) {
|
||||
$scope.workplace = wp;
|
||||
emAuth.set('workplace', wp);
|
||||
};
|
||||
|
||||
$scope.setEmail = function(eml) {
|
||||
$scope.email = eml;
|
||||
};
|
||||
|
||||
$scope.setTemplate = function(tpl) {
|
||||
$scope.template = tpl;
|
||||
}
|
||||
|
||||
$scope.setDraft = function(drf) {
|
||||
$scope.draft = drf;
|
||||
};
|
||||
|
||||
$scope.setWPS = function(wps) {
|
||||
$scope.workplaces = wps;
|
||||
};
|
||||
|
||||
$scope.setItemParams = function(params) {
|
||||
$scope.itemParams = params;
|
||||
};
|
||||
|
||||
$scope.setSub = function(s) {
|
||||
$scope.sub = s;
|
||||
}
|
||||
|
||||
$scope.compose = function() {
|
||||
(new EmDrafts()).createDraft(function(drf){
|
||||
window.location.hash = '#/workplaces/'+emAuth.get('workplace').ID+'/drafts/'+drf.ID+'/edit';
|
||||
});
|
||||
};
|
||||
|
||||
// preview for mobile devices
|
||||
|
||||
$scope.showPreview = false;
|
||||
$scope.togglePreview = function() {
|
||||
$scope.showPreview = !$scope.showPreview;
|
||||
};
|
||||
|
||||
$scope.showExclusionList = false;
|
||||
$scope.toggleExclusionList = function() {
|
||||
$scope.showExclusionList = !$scope.showExclusionList;
|
||||
};
|
||||
}
|
||||
|
||||
var LS = (function(){
|
||||
|
||||
var l = window.localStorage;
|
||||
|
||||
|
||||
return {
|
||||
get: function(key) {
|
||||
var v = l.getItem(key);
|
||||
if ( v && v.indexOf('__JSON__') === 0 ) {
|
||||
var vObj = null;
|
||||
try {
|
||||
vObj = JSON.parse(v.substr(8))
|
||||
v = vObj;
|
||||
} catch(e) {
|
||||
console.error('cannot parse localStorage value '+v);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
},
|
||||
set: function(key, value) {
|
||||
if ( typeof value !== 'string' ) {
|
||||
value = '__JSON__'+JSON.stringify(value);
|
||||
}
|
||||
l.setItem(key, value);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function LoginCtrl($window, $scope, $http, emAuth, synConn) {
|
||||
|
||||
var u = (window.location+'').match(/http(s|)\:\/\/([^\/]+)(?:\:(\d+)|)/);
|
||||
|
||||
var port = u[3] || null,
|
||||
secure = (u[1] == 's');
|
||||
|
||||
if ( !port ) {
|
||||
port = secure ? '443' : '50888';
|
||||
} else if ( port == '443' ) {
|
||||
secure = true;
|
||||
}
|
||||
|
||||
$scope.conn = {
|
||||
host: u[2]+':'+port,
|
||||
email: '',
|
||||
password: '',
|
||||
secure: secure
|
||||
};
|
||||
|
||||
$scope.remember = false;
|
||||
|
||||
$scope.savedHosts = LS.get('savedHosts') || [];
|
||||
var lastUsedHostID = LS.get('lastUsedHostID');
|
||||
|
||||
if ( lastUsedHostID !== null ) {
|
||||
$scope.conn = angular.extend({}, $scope.savedHosts[lastUsedHostID]);
|
||||
if ( typeof $scope.conn.secure === 'undefined' ) {
|
||||
$scope.conn.secure = !!$scope.conn.host.match(/\:443$/);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$scope.loadHost = function(idx){
|
||||
var h = $scope.savedHosts[idx];
|
||||
if ( h ) {
|
||||
$scope.conn = angular.extend({}, h);
|
||||
lastUsedHostID = idx;
|
||||
} else {
|
||||
console.error('cannot load host data with index '+idx);
|
||||
}
|
||||
};
|
||||
|
||||
function isHostSaved(hostData) {
|
||||
var saved = false;
|
||||
|
||||
angular.forEach($scope.savedHosts, function(conn, i){
|
||||
if (
|
||||
conn.host == hostData.host &&
|
||||
conn.email == hostData.email &&
|
||||
conn.password == hostData.password
|
||||
) {
|
||||
saved = i;
|
||||
return false; // breaking the loop
|
||||
}
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
$scope.signIn = function() {
|
||||
|
||||
var hostData = {
|
||||
host: $scope.conn.host.trim(),
|
||||
email: $scope.conn.email.trim(),
|
||||
password: $scope.conn.password.trim()
|
||||
};
|
||||
|
||||
var hostSaved = isHostSaved(hostData);
|
||||
|
||||
if ( $scope.remember ) {
|
||||
if ( hostSaved === false ) {
|
||||
$scope.savedHosts.push(hostData);
|
||||
LS.set('savedHosts', $scope.savedHosts);
|
||||
lastUsedHostID = $scope.savedHosts.length-1;
|
||||
} else {
|
||||
lastUsedHostID = hostSaved;
|
||||
}
|
||||
}
|
||||
|
||||
if ( lastUsedHostID !== null ) {
|
||||
LS.set('lastUsedHostID', lastUsedHostID);
|
||||
}
|
||||
|
||||
var arr = $scope.conn.host.split(':'),
|
||||
host = arr[0],
|
||||
port = arr[1] || '50888';
|
||||
|
||||
emAuth
|
||||
.set('host', host)
|
||||
.set('port', port)
|
||||
.set('email', $scope.conn.email)
|
||||
.set('password', $scope.conn.password)
|
||||
.set('secure', $scope.conn.secure);
|
||||
|
||||
synConn.setOpts({
|
||||
host: host,
|
||||
port: port,
|
||||
email: $scope.conn.email,
|
||||
password: $scope.conn.password,
|
||||
secure: $scope.conn.secure
|
||||
});
|
||||
|
||||
synConn.touch('wps', function(err, sa){
|
||||
if ( err ) {
|
||||
$scope.error = err;
|
||||
} else {
|
||||
emAuth.setAuthenticated(true);
|
||||
|
||||
$scope.setUserName(sa.User);
|
||||
$window.location.hash = '#/workplaces';
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function WorkplacesCtrl($scope, $stateParams, wps) {
|
||||
//var wps = $route.current.locals.wps;
|
||||
$scope.setWPS(wps);
|
||||
}
|
||||
|
||||
WorkplacesCtrl.resolve = {
|
||||
wps: function($q, $http, synConn) {
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wps/GetUserWorkplaces?UserName={email}' })
|
||||
.success(function(data){
|
||||
defer.resolve(data);
|
||||
})
|
||||
.error(function(data, status, headers, config){
|
||||
defer.reject(data);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
}
|
||||
|
||||
app.controller('TopToolbarCtrl', function($scope){
|
||||
//console.log('TopToolbarCtrl section: '+$scope.section);
|
||||
//$scope.itemPath
|
||||
});
|
||||
|
||||
app.controller('SidebarMenuCtrl', function($scope, emCategories){
|
||||
var cats = emCategories.getAll();
|
||||
|
||||
if ( cats.then ) {
|
||||
cats.then(function(c){
|
||||
$scope.templateCats = c;
|
||||
});
|
||||
} else {
|
||||
$scope.templateCats = cats;
|
||||
}
|
||||
});
|
||||
|
||||
function openPreview(item) {
|
||||
|
||||
// // var zoomStyle = ['<style>',
|
||||
// // 'html { ',
|
||||
// // '-ms-zoom: 0.30;',
|
||||
// // '-moz-transform:scale(0.30);',
|
||||
// // '-moz-tansform-origin: 0 0;',
|
||||
// // '-o-transform: scale(0.30);',
|
||||
// // '-o-transform-origin: 0 0;',
|
||||
// // '-webkit-transform: scale(0.30);',
|
||||
// // '-webkit-transform-origin: 0 0;',
|
||||
// // '}</style>'].join('');
|
||||
|
||||
// // zoomStyle = []; console.warn('zoom style disabled');
|
||||
|
||||
|
||||
// // data.EmailSource = data.EmailSource.replace(/(<\/head>)/i, zoomStyle+'$1');
|
||||
// // data.EmailSource = emUtils.expandAssetURLs(data.EmailSource, data.BaseURL);
|
||||
|
||||
if ( item.then ) {
|
||||
item.then(
|
||||
function(itm){
|
||||
openPreview(itm);
|
||||
},
|
||||
function(err){
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var body = item.getBody();
|
||||
|
||||
if ( body.then ) {
|
||||
body.then(
|
||||
function(b){
|
||||
setIFrameContent(b);
|
||||
},
|
||||
function(err){
|
||||
if ( err instanceof URIError || (err+'').match(/URI\s+malformed/i) ) {
|
||||
err = 'cannot unpack EmailSource';
|
||||
}
|
||||
setIFrameContent('An error occured: '+err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setIFrameContent(body);
|
||||
}
|
||||
}
|
||||
|
||||
function setIFrameContent(data) {
|
||||
|
||||
var iframeId = 'preview-container-iframe',
|
||||
container = document.getElementById('preview-container'),
|
||||
iframe = document.getElementById(iframeId);
|
||||
|
||||
if ( !container ) { return; }
|
||||
|
||||
if ( !iframe ) {
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
container.appendChild(iframe);
|
||||
|
||||
iframe.id = iframeId;
|
||||
}
|
||||
|
||||
var doc = iframe.document;
|
||||
if ( iframe.contentDocument ) {
|
||||
doc = iframe.contentDocument; // For NS6
|
||||
} else if(iframe.contentWindow) {
|
||||
doc = iframe.contentWindow.document; // For IE5.5 and IE6
|
||||
}
|
||||
|
||||
// Put the content in the iframe
|
||||
doc.open();
|
||||
doc.writeln(data);
|
||||
doc.close();
|
||||
}
|
||||
|
||||
function CtrlConsole($scope, synConn) {
|
||||
|
||||
$scope.params = [];
|
||||
$scope.headers = [];
|
||||
|
||||
$scope.method = 'GET';
|
||||
$scope.uri = '/wp/1/settings/Drafts?Select=*&where=deleted=0&STARTINDEX=0&RESULTS=10';
|
||||
$scope.contentType = '';
|
||||
|
||||
$scope.response = '';
|
||||
|
||||
$scope.useRawBody = false;
|
||||
$scope.rawBodyBtn = 'Use raw body';
|
||||
$scope.rawBody = '';
|
||||
|
||||
$scope.$watch('useRawBody', function(use){
|
||||
$scope.rawBodyBtn = $scope.useRawBody ? 'Don\'t use raw body' : 'Use raw body';
|
||||
});
|
||||
|
||||
$scope.addParam = function() {
|
||||
$scope.useRawBody = false;
|
||||
$scope.params.push({
|
||||
name: '',
|
||||
value: ''
|
||||
})
|
||||
};
|
||||
|
||||
$scope.toggleRawBody = function(){
|
||||
$scope.useRawBody = !$scope.useRawBody;
|
||||
|
||||
if ( $scope.useRawBody ) {
|
||||
$scope.params = [];
|
||||
}
|
||||
};
|
||||
|
||||
$scope.delParam = function(idx) {
|
||||
$scope.params.splice(idx, 1);
|
||||
};
|
||||
|
||||
$scope.addHeader = function() {
|
||||
$scope.headers.push({
|
||||
name: '',
|
||||
value: ''
|
||||
})
|
||||
};
|
||||
|
||||
$scope.delHeader = function(idx) {
|
||||
$scope.headers.splice(idx, 1);
|
||||
};
|
||||
|
||||
$scope.sendRequest = function() {
|
||||
var uri = $scope.uri;
|
||||
if ( uri[0] === '/' ) {
|
||||
uri = uri.substr(1);
|
||||
}
|
||||
|
||||
var params = $scope.params.length ? {} : null;
|
||||
angular.forEach($scope.params, function(p){
|
||||
params[p.name] = p.value;
|
||||
});
|
||||
|
||||
var headers = {};
|
||||
if ( $scope.contentType ) {
|
||||
headers['Content-Type'] = $scope.contentType;
|
||||
}
|
||||
|
||||
angular.forEach($scope.headers, function(h){
|
||||
headers[h.name] = h.value;
|
||||
});
|
||||
|
||||
var opts = {
|
||||
method: $scope.method,
|
||||
uri: uri,
|
||||
headers: headers,
|
||||
synReq: true
|
||||
};
|
||||
|
||||
if ( params ) {
|
||||
opts[ ( opts.method === 'GET' ) ? 'params' : 'data' ] = params;
|
||||
}
|
||||
|
||||
if ( $scope.useRawBody ) {
|
||||
opts.data = $scope.rawBody;
|
||||
}
|
||||
|
||||
synConn
|
||||
.http(opts)
|
||||
.success(function(data){
|
||||
if ( typeof data !== 'string' ) {
|
||||
data = JSON.stringify(data, null, '\t');
|
||||
}
|
||||
$scope.response = data;
|
||||
})
|
||||
.error(function(err){
|
||||
$scope.$apply(function(){
|
||||
$scope.response = err.status+' : '+err.message;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}
|
@@ -0,0 +1,309 @@
|
||||
app.controller('ContactsCtrl', function($scope, $stateParams, EmContacts, EmContactsEL){
|
||||
if ( $stateParams.gid ) {
|
||||
$scope.contacts = new EmContacts({
|
||||
groupId: parseInt($stateParams.gid, 10),
|
||||
placement: $stateParams.placement
|
||||
});
|
||||
}
|
||||
|
||||
if ( $stateParams.gid ) {
|
||||
$scope.contactsExcList = new EmContactsEL({
|
||||
groupId: parseInt($stateParams.gid, 10)
|
||||
});
|
||||
}
|
||||
|
||||
if ( $scope.contacts ) {
|
||||
$scope.contacts.nextPage();
|
||||
}
|
||||
if ( $scope.contactsExcList ) {
|
||||
$scope.contactsExcList.nextPage();
|
||||
}
|
||||
});
|
||||
|
||||
app.factory('EmContacts', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmDrf) {
|
||||
var EmContacts = function(opts) {
|
||||
opts = opts || {};
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.after = '';
|
||||
this.options = {
|
||||
groupId: null
|
||||
};
|
||||
|
||||
this.noMoreData = false;
|
||||
|
||||
angular.extend(this.options, opts);
|
||||
};
|
||||
|
||||
function resolveGroups(drafts, done) {
|
||||
var draftsToProcess = drafts.length;
|
||||
|
||||
for (var i = 0; i < drafts.length; i++) {
|
||||
emGroups.resolve(drafts[i].GroupList, function(err, resolved){
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
try {
|
||||
drafts[this.i].GroupList = JSON.parse(drafts[this.i].GroupList);
|
||||
} catch (e) {
|
||||
drafts[this.i].GroupList = [];
|
||||
}
|
||||
drafts[this.i].GroupListResolved = resolved;
|
||||
//drafts[this.i].GroupList = resolved;
|
||||
|
||||
emAccounts.resolve(drafts[this.i].AccountList, function(err, resolvedAccounts){
|
||||
if (err) { return console.log(err); }
|
||||
|
||||
try {
|
||||
drafts[this.i].AccountList = JSON.parse(drafts[this.i].AccountList) || [];
|
||||
} catch (e) {
|
||||
drafts[this.i].AccountList = [];
|
||||
}
|
||||
drafts[this.i].AccountListResolved = resolvedAccounts;
|
||||
//drafts[this.i].AccountList = resolvedAccounts;
|
||||
|
||||
draftsToProcess -= 1;
|
||||
if ( draftsToProcess === 0 ) {
|
||||
done(drafts);
|
||||
}
|
||||
}.bind({ i: this.i }));
|
||||
}.bind({ i: i }));
|
||||
}
|
||||
}
|
||||
|
||||
function parseFields(fields) {
|
||||
if ( typeof fields === 'string' ) {
|
||||
try {
|
||||
fields = JSON.parse(fields);
|
||||
} catch(e) {
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
var res = {};
|
||||
angular.forEach(fields, function(fd){
|
||||
res[fd.Name] = fd.Value;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function reformatResponseObj(o) {
|
||||
var res = {
|
||||
Fields: {}
|
||||
};
|
||||
var directMappingFields = [
|
||||
'Email',
|
||||
'First_Name',
|
||||
'Last_Name',
|
||||
'Recipient_Name',
|
||||
'Subscribed',
|
||||
'Subscribe_Date'
|
||||
];
|
||||
|
||||
function createRX(fieldName) {
|
||||
var rxStr = '^'+fieldName.replace(/W+/, '.*?').toLowerCase()+'$';
|
||||
return new RegExp(rxStr, 'i');
|
||||
}
|
||||
|
||||
angular.forEach(directMappingFields, function(dmf){
|
||||
var rx = createRX(dmf);
|
||||
for ( var p in o ) {
|
||||
if ( rx.exec(p) ) {
|
||||
res[dmf] = o[p];
|
||||
delete o[p];
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
res.Fields = o;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
EmContacts.prototype.getExclusionList = function(done) {
|
||||
done = done || function(){};
|
||||
|
||||
if ( this._exclusionList ) {
|
||||
return done(null, this._exclusionList);
|
||||
} else {
|
||||
var start = 0;
|
||||
var uri = 'wp/{wpId}/group/ExclusionList?SELECT=*&WHERE=GroupID='+this.options.groupId+'&STARTINDEX='+start+'&RESULTS=25';
|
||||
|
||||
//var uri = 'Select Email, Date_Added from ExclusionList WHERE GROUPID=ID LIMIT 25 OFFSET 0"';
|
||||
|
||||
synConn
|
||||
.http({ uri: uri })
|
||||
.success(function(data){
|
||||
done(null, data);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
EmContacts.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.busy ) return;
|
||||
that.busy = true;
|
||||
|
||||
var start = this.items.length;
|
||||
|
||||
var groupWhere = (typeof this.options.groupId !== 'undefined' && this.options.groupId !== null)
|
||||
? '&where=GroupID='+this.options.groupId
|
||||
: '';
|
||||
var uri,
|
||||
isLocalGroup = this.options.placement == 'local';
|
||||
|
||||
if ( groupWhere ) {
|
||||
this.getExclusionList();
|
||||
}
|
||||
|
||||
if ( isLocalGroup ) {
|
||||
uri = 'wp/{wpId}/group/Emails?SELECT=*'+groupWhere+'&STARTINDEX='+start+'&RESULTS=25';
|
||||
} else { // remote
|
||||
uri = 'wp/{wpId}/settings/ConnectToDB?ID='+this.options.groupId+'&STARTINDEX='+start+'&RESULTS=25';
|
||||
}
|
||||
|
||||
synConn
|
||||
.http({ uri: uri })
|
||||
.success(function(data){
|
||||
|
||||
// console.warn('Got data: '+data.length);
|
||||
// console.log(data);
|
||||
|
||||
// no more emails
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
} else {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if ( isLocalGroup ) {
|
||||
if ( data[i].Fields ) {
|
||||
data[i].Fields = parseFields(data[i].Fields);
|
||||
}
|
||||
} else { // remote group
|
||||
data[i] = reformatResponseObj(data[i]);
|
||||
}
|
||||
that.items.push(data[i]);
|
||||
}
|
||||
that.busy = false;
|
||||
defer.resolve();
|
||||
}
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
console.error(err)
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
return EmContacts;
|
||||
});
|
||||
|
||||
app.factory('EmContactsEL', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmDrf) {
|
||||
var EmContactsEL = function(opts) {
|
||||
opts = opts || {};
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.after = '';
|
||||
this.options = {
|
||||
groupId: null
|
||||
};
|
||||
|
||||
this.noMoreData = false;
|
||||
|
||||
angular.extend(this.options, opts);
|
||||
};
|
||||
|
||||
EmContactsEL.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.busy ) return;
|
||||
that.busy = true;
|
||||
|
||||
var start = this.items.length;
|
||||
var uri = 'wp/{wpId}/group/ExclusionList?SELECT=*&WHERE=GroupID='+this.options.groupId+'&STARTINDEX='+start+'&RESULTS=25';
|
||||
|
||||
synConn
|
||||
.http({ uri: uri })
|
||||
.success(function(data){
|
||||
|
||||
// console.warn('Got data: '+data.length);
|
||||
// console.log(data);
|
||||
|
||||
// no more items
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
} else {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
that.items.push(data[i]);
|
||||
}
|
||||
that.busy = false;
|
||||
defer.resolve();
|
||||
}
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
return EmContactsEL;
|
||||
});
|
||||
|
||||
app.controller('ContactsSidebarMenuCtrl', function($scope, $stateParams, groups){
|
||||
var sortedGroups = {
|
||||
local: [],
|
||||
db: []
|
||||
};
|
||||
|
||||
$scope.wpid = $stateParams.wpid;
|
||||
|
||||
angular.forEach(groups, function(g){
|
||||
sortedGroups[ (g.GroupKind == 1) ? 'local':'db' ].push(g);
|
||||
});
|
||||
|
||||
$scope.groups = sortedGroups;
|
||||
});
|
||||
|
||||
var ContactsSidebarMenuCtrlResolve = {
|
||||
groups: function($q, synConn) {
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({
|
||||
uri: 'wp/{wpId}/settings/Groups?Select=ID,GroupName,GroupKind&WHERE=Hidden=0'
|
||||
})
|
||||
.success(function(data){
|
||||
angular.forEach(data, function(g){
|
||||
console.log(g.GroupKind);
|
||||
switch ( g.GroupKind ) {
|
||||
case 1: // internal
|
||||
g.type = 'My Groups';
|
||||
g.placement = 'local';
|
||||
break;
|
||||
case 0: // external
|
||||
case 2: //wpnewsman
|
||||
g.type = 'My Databases';
|
||||
g.placement = 'remote';
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
defer.resolve(data);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
};
|
@@ -0,0 +1,549 @@
|
||||
function DraftsCtrl($scope, $http, $stateParams, EmDrafts, emAuth, synConn, emUtils) {
|
||||
|
||||
$scope.drafts = new EmDrafts({ showDeleted: $scope.subSection === 'trash' });
|
||||
|
||||
$scope.drfClick = function(drf) {
|
||||
$scope.drafts.activeItem = drf.ID;
|
||||
$scope.setEmail(drf);
|
||||
|
||||
openPreview(drf);
|
||||
};
|
||||
|
||||
$scope.drfDelete = function(ev, drf){
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var delFromTrash = ($scope.subSection === 'trash');
|
||||
if ( delFromTrash && !confirm('Are sure you want to delete this draft?') ) { return; }
|
||||
drf.del(function(err){
|
||||
if ( !err ) {
|
||||
$scope.drafts.del(drf);
|
||||
}
|
||||
}, delFromTrash);
|
||||
};
|
||||
|
||||
$scope.drfRestore = function(ev, drf){
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
drf.restore(function(err){
|
||||
if ( !err ) {
|
||||
$scope.drafts.del(drf);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
app.controller('DraftEditorCtrl', function($scope, $controller, $modal, $stateParams, emAuth, synConn, emUtils, emCategories, EmTemplates, EmDrafts, accounts, groups){
|
||||
$controller('CommonEditorController', { $scope: $scope });
|
||||
|
||||
$scope.stateParams = $stateParams;
|
||||
|
||||
var gotScopeItem;
|
||||
|
||||
$scope.itemType = '';
|
||||
$scope.accounts = accounts;
|
||||
$scope.account = [];
|
||||
|
||||
$scope.$watch('account', function(newVal){
|
||||
if ( $scope.item ) {
|
||||
var nv = parseInt(newVal, 10);
|
||||
$scope.item.AccountList = !isNaN(nv) ? [nv] : [];
|
||||
}
|
||||
});
|
||||
|
||||
$scope.groups = groups;
|
||||
$scope.to = [];
|
||||
|
||||
$scope.$watch('item', function(newVal){
|
||||
if ( typeof newVal !== 'undefined' && !newVal.then ) {
|
||||
$scope.to = $scope.item.GroupList;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('to', function(newVal){
|
||||
if ( $scope.item ) {
|
||||
$scope.item.GroupList = newVal
|
||||
}
|
||||
});
|
||||
|
||||
$scope.enableTracking = false;
|
||||
|
||||
$scope.$watch('item', function(item){
|
||||
if ( typeof item !== 'undefined' && !item.then ) {
|
||||
$scope.enableTracking = item.TrackMethod > 0;
|
||||
}
|
||||
});
|
||||
|
||||
if ( $stateParams.draft ) {
|
||||
$scope.itemType = 'draft';
|
||||
|
||||
$scope.setItem( (new EmDrafts()).getDraft($stateParams.draft) );
|
||||
|
||||
var p = { root: 'drafts' };
|
||||
|
||||
gotScopeItem = function(drf) {
|
||||
//p.name = drf.Subject;
|
||||
p.item = drf;
|
||||
$scope.setItemParams(p);
|
||||
//$scope.item = drf;
|
||||
$scope.setItem(drf);
|
||||
|
||||
$scope.account = drf.AccountList[0];
|
||||
window.drf = drf;
|
||||
};
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
if ( $scope.item.then ) {
|
||||
$scope.item.then(
|
||||
gotScopeItem,
|
||||
function(err){
|
||||
console.error(err+'');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
gotScopeItem($scope.item);
|
||||
}
|
||||
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.item.save(function(err){
|
||||
console.log(arguments);
|
||||
if ( !err ) {
|
||||
$scope.alert({ type:'success', msg: 'Successfully saved' });
|
||||
} else {
|
||||
$scope.alert({ type:'danger', msg: err+'' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sendNow = function(){
|
||||
$scope.item.save(function(err){
|
||||
if ( !err ) {
|
||||
$scope.item.startSending(function(err, outboxEmlID){
|
||||
if ( !err ) {
|
||||
window.location.hash = '#/workplaces/'+emAuth.get('workplace').ID+'/outbox/'+outboxEmlID+'/preview';
|
||||
} else {
|
||||
$scope.alert({
|
||||
type:'danger',
|
||||
msg: err+''
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$scope.alert({
|
||||
type:'danger',
|
||||
msg: 'Error while saving draft: '+err
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAttachment = function(fileName) {
|
||||
$scope.item.removeAttachment(fileName, function(err){
|
||||
if ( err ) { return console.error(err); }
|
||||
for (var i = $scope.item.AttachList.length - 1; i >= 0; i--) {
|
||||
if ( $scope.item.AttachList[i] == fileName ) {
|
||||
$scope.item.AttachList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$scope.item.save();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var DraftEditorResolve = {
|
||||
accounts: function($q, synConn) {
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({
|
||||
uri: 'wp/{wpId}/settings/Account?Select=AccountName,ID'
|
||||
})
|
||||
.success(function(data){
|
||||
defer.resolve(data);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
},
|
||||
groups: function($q, synConn) {
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({
|
||||
uri: 'wp/{wpId}/settings/Groups?Select=ID,GroupName,GroupKind&WHERE=Hidden=0'
|
||||
})
|
||||
.success(function(data){
|
||||
angular.forEach(data, function(g){
|
||||
g.type = (g.GroupKind == 1) ? 'My Groups' : 'My Databases';
|
||||
});
|
||||
defer.resolve(data);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
};
|
||||
|
||||
app.factory('EmDrf', function($http, $q, synConn, emUtils, emAuth){
|
||||
var EmDrf = function(data){
|
||||
var defaults = {
|
||||
AccountList: [],
|
||||
PersonalAttachList: [],
|
||||
CID: 0,
|
||||
CName: "",
|
||||
CharSet: "UTF-8",
|
||||
ConfirmRead: 0,
|
||||
Deleted: 0,
|
||||
GroupList: [],
|
||||
Guid: '{'+emUtils.UUID()+'}',
|
||||
ID: 0,
|
||||
MailTo: "",
|
||||
MessageFormat: 0, // 1 - html, 0 - plain
|
||||
ModTime: (new Date()).getTime(),
|
||||
Priority: 3,
|
||||
Subject: "Please Enter Subject Here",
|
||||
TrackMethod: 0,
|
||||
TrackName: "",
|
||||
UseAnalytics: 0,
|
||||
};
|
||||
|
||||
angular.extend(this, defaults);
|
||||
|
||||
if ( typeof data !== 'undefined' ) {
|
||||
angular.extend(this, data);
|
||||
}
|
||||
|
||||
this.ModTime = emUtils.timeLogToDate(this.ModTime);
|
||||
|
||||
this.GroupList = this._map(this.GroupList, function(itm){ return parseInt(itm, 10); });
|
||||
this.AccountList = this._map(this.AccountList, function(itm){ return parseInt(itm, 10); });
|
||||
};
|
||||
var p = EmDrf.prototype;
|
||||
|
||||
p.getBaseURL = function() {
|
||||
return '/wps/drafts/'+emAuth.get('workplace').ID+'/'+this.Guid+'/';
|
||||
};
|
||||
|
||||
p._map = function(arr, mapper) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
arr[i] = mapper(arr[i], i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
p.del = function(done, forever) {
|
||||
var req;
|
||||
if ( forever ) {
|
||||
req = {
|
||||
method: 'DELETE',
|
||||
uri: 'wp/{wpId}/settings/drafts/'+this.ID
|
||||
};
|
||||
} else {
|
||||
req = {
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/drafts/'+this.ID,
|
||||
data: {
|
||||
deleted: 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
synConn
|
||||
.http(req)
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
||||
p.restore = function(done) {
|
||||
synConn
|
||||
.http({
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/drafts/'+this.ID,
|
||||
data: {
|
||||
deleted: 0
|
||||
}
|
||||
})
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
||||
p.save = function(done) {
|
||||
var _this = this;
|
||||
done = done || function(){};
|
||||
|
||||
var obj = {
|
||||
'ID': this.ID,
|
||||
'CharSet': 'UTF-8',
|
||||
'Subject': this.Subject,
|
||||
|
||||
'MessageFormat': 0, // 0 - html, 1 - plain
|
||||
'Guid': this.Guid,
|
||||
'EmailSource': emUtils.shrinkAssetURLs(this.EmailSource, this.getBaseURL()),
|
||||
'Name': this.TemplateName,
|
||||
|
||||
'TrackMethod': parseInt(this.TrackMethod, 10), // - numeric, values (0: not used; 1: google; 2: piwik)
|
||||
'TrackName': this.TrackName, // - string, tracking campaign name
|
||||
|
||||
'AccountList': this._map(this.AccountList, function(itm){ return itm+''; }), // converting array of nums into arr of strings
|
||||
'GroupList': this._map(this.GroupList, function(itm){ return itm+''; }), // ~
|
||||
'AttachList': this.AttachList,
|
||||
'PersonalAttachList': this.PersonalAttachList
|
||||
};
|
||||
|
||||
synConn
|
||||
.http({
|
||||
method: 'POST',
|
||||
uri: 'wp/{wpId}/settings/SetDraftMessage',
|
||||
data: obj
|
||||
})
|
||||
.success(function(data){
|
||||
_this.ID = data.ID;
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
p.setBody = function(body) {
|
||||
this.EmailSource = body;
|
||||
}
|
||||
|
||||
p.getBody = function() {
|
||||
var _this = this;
|
||||
if ( typeof this._data !== 'undefined' ) {
|
||||
return this.EmailSource;
|
||||
}
|
||||
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetDraftMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
_this._data = data;
|
||||
|
||||
data.EmailSource = emUtils.unpackEmailSource(data.EmailSource);
|
||||
|
||||
//data.BaseURL = emUtils.supplant('/wps/drafts/{wpid}/{guid}/',{ guid: data.Guid, wpid: emAuth.get('workplace').ID });
|
||||
_this.EmailSource = emUtils.expandAssetURLs(data.EmailSource, _this.getBaseURL());
|
||||
defer.resolve(_this.EmailSource);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
p.startSending = function(done) {
|
||||
done = done || function(){};
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/SendDraftMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
|
||||
console.warn('SendDraftMessage');
|
||||
console.log(data);
|
||||
done(null, data.ID);
|
||||
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
// SendDraftMessage
|
||||
// param - ID
|
||||
// StopSending - ID
|
||||
// SendMessage - ID
|
||||
};
|
||||
|
||||
p.removeAttachment = function(fileName, done) {
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/RemoveContent?GUID='+this.Guid+'&type=drafts&Folder=attachment&FileName='+fileName })
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
p.addAttachment = function(fileName) {
|
||||
var idx = this.AttachList.indexOf(fileName);
|
||||
if ( idx === -1 ) {
|
||||
this.AttachList.push(fileName);
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
return EmDrf;
|
||||
});
|
||||
|
||||
app.factory('EmDrafts', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmDrf) {
|
||||
var EmDrafts = function(opts) {
|
||||
opts = opts || {};
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.after = '';
|
||||
this.options = {
|
||||
showDeleted: false
|
||||
};
|
||||
|
||||
this.noMoreData = false;
|
||||
|
||||
angular.extend(this.options, opts);
|
||||
};
|
||||
|
||||
function resolveGroups(drafts, done) {
|
||||
var draftsToProcess = drafts.length;
|
||||
|
||||
for (var i = 0; i < drafts.length; i++) {
|
||||
emGroups.resolve(drafts[i].GroupList, function(err, resolved){
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
try {
|
||||
drafts[this.i].GroupList = JSON.parse(drafts[this.i].GroupList);
|
||||
} catch (e) {
|
||||
drafts[this.i].GroupList = [];
|
||||
}
|
||||
drafts[this.i].GroupListResolved = resolved;
|
||||
//drafts[this.i].GroupList = resolved;
|
||||
|
||||
emAccounts.resolve(drafts[this.i].AccountList, function(err, resolvedAccounts){
|
||||
if (err) { return console.log(err); }
|
||||
|
||||
try {
|
||||
drafts[this.i].AccountList = JSON.parse(drafts[this.i].AccountList) || [];
|
||||
} catch (e) {
|
||||
drafts[this.i].AccountList = [];
|
||||
}
|
||||
drafts[this.i].AccountListResolved = resolvedAccounts;
|
||||
//drafts[this.i].AccountList = resolvedAccounts;
|
||||
|
||||
draftsToProcess -= 1;
|
||||
if ( draftsToProcess === 0 ) {
|
||||
done(drafts);
|
||||
}
|
||||
}.bind({ i: this.i }));
|
||||
}.bind({ i: i }));
|
||||
}
|
||||
}
|
||||
|
||||
EmDrafts.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.busy ) return;
|
||||
that.busy = true;
|
||||
|
||||
//var sql = 'ID, Subject, MailTo, Total, Processed, Sent, Excluded, Failed, Status, StatusMessage, CreateTime, UseAnalytics, CID, AccountList, GroupList, Deleted, Log From Outbox Order by CreateTime DESC LIMIT 10';
|
||||
var sql = '*';
|
||||
|
||||
var start = this.items.length;
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/Drafts?Select=*&where=deleted='+(this.options.showDeleted ? '1' : '0')+'&STARTINDEX='+start+'&RESULTS=10&SORT=ModTime&DIR=DESC' })
|
||||
.success(function(data){
|
||||
|
||||
// no more emails
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
} else {
|
||||
resolveGroups(data, function(drafts){
|
||||
for (var i = 0; i < drafts.length; i++) {
|
||||
that.items.push(new EmDrf(drafts[i]));
|
||||
}
|
||||
that.busy = false;
|
||||
defer.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
console.error(err)
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
EmDrafts.prototype.getDraft = function(id) {
|
||||
var tpl;
|
||||
|
||||
if ( this.items ) {
|
||||
angular.forEach(this.items, function(t, i){
|
||||
if ( t.ID == id ) {
|
||||
tpl = t;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if ( tpl ) {
|
||||
return tpl;
|
||||
}
|
||||
}
|
||||
|
||||
var defer = $q.defer();
|
||||
synConn
|
||||
//.http({ uri: 'wp/{wpId}/settings/Drafts?Select=*&where=ID='+id+'&RESULTS=1' })
|
||||
.http({ uri: 'wp/{wpId}/settings/GetDraftMessage?ID='+id })
|
||||
.success(function(data){
|
||||
defer.resolve( new EmDrf(data) );
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
EmDrafts.prototype.createDraft = function(done) {
|
||||
var _this = this;
|
||||
synConn
|
||||
.http({
|
||||
method: 'GET',
|
||||
uri: 'wp/{wpId}/settings/CreateNewMessage'
|
||||
})
|
||||
.success(function(draft){
|
||||
console.log('createDraft results:');
|
||||
console.log(draft);
|
||||
var drf = new EmDrf(draft)
|
||||
_this.items.push(drf);
|
||||
done(drf);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
EmDrafts.prototype.del = function(drf) {
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
if ( this.items[i] === drf ) {
|
||||
this.items.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return EmDrafts;
|
||||
});
|
||||
|
@@ -0,0 +1,109 @@
|
||||
app.controller('ModalUploadAttachmentCtrl', function($scope, $modalInstance, emAuth, grfFiles, editItem, itemType, synConn){
|
||||
|
||||
$scope.files = grfFiles.items;
|
||||
|
||||
var uploadType;
|
||||
switch ( itemType ) {
|
||||
case 'template':
|
||||
uploadType = 'templates';
|
||||
break;
|
||||
case 'draft':
|
||||
uploadType = 'drafts';
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
grfFiles.attrs.action = synConn.getBaseURL()+'wp/'+emAuth.get('workplace').ID+'/settings/UploadContent?'+[
|
||||
'GUID='+editItem.Guid,
|
||||
'type='+uploadType,
|
||||
'folder=attachment'
|
||||
].join('&');
|
||||
|
||||
$scope.close = function () {
|
||||
var fileNames = [];
|
||||
angular.forEach($scope.files, function(f){
|
||||
fileNames.push(f.fileName);
|
||||
})
|
||||
$modalInstance.dismiss(fileNames);
|
||||
};
|
||||
|
||||
$scope.setFiles = function(element) {
|
||||
$scope.$apply(function($scope) {
|
||||
// Turn the FileList object into Array
|
||||
$scope.files = [];
|
||||
for (var i = 0; i < element.files.length; i++) {
|
||||
$scope.files.push(element.files[i])
|
||||
}
|
||||
$scope.progressVisible = false
|
||||
});
|
||||
};
|
||||
|
||||
$scope.uploadFile = function() {
|
||||
var fd = new FormData()
|
||||
for (var i in $scope.files) {
|
||||
fd.append("uploadedFile", $scope.files[i])
|
||||
}
|
||||
fd.append('path', $scope.upload.path);
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.upload.addEventListener("progress", uploadProgress, false)
|
||||
xhr.addEventListener("load", uploadComplete, false)
|
||||
xhr.addEventListener("error", uploadFailed, false)
|
||||
xhr.addEventListener("abort", uploadCanceled, false)
|
||||
xhr.open('PUT', '/wp/'+emAuth.workplace.ID+'/settings/UploadContent')
|
||||
$scope.progressVisible = true
|
||||
xhr.send(fd)
|
||||
}
|
||||
|
||||
function uploadProgress(evt) {
|
||||
$scope.$apply(function(){
|
||||
if (evt.lengthComputable) {
|
||||
$scope.progress = Math.round(evt.loaded * 100 / evt.total)
|
||||
} else {
|
||||
$scope.progress = 'unable to compute'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function uploadComplete(evt) {
|
||||
/* This event is raised when the server send back a response */
|
||||
$modalInstance.close(evt.target.responseText);
|
||||
}
|
||||
|
||||
function uploadFailed(evt) {
|
||||
alert("There was an error attempting to upload the file.")
|
||||
}
|
||||
|
||||
function uploadCanceled(evt) {
|
||||
$scope.$apply(function(){
|
||||
$scope.progressVisible = false
|
||||
})
|
||||
alert("The upload has been canceled by the user or the browser dropped the connection.")
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.controller('CommonEditorController', function($scope, $modal){
|
||||
$scope.addCommonAtt = function() {
|
||||
|
||||
var modalInstance = $modal.open({
|
||||
templateUrl: 'partials/modal-upload-attachment.html',
|
||||
controller: 'ModalUploadAttachmentCtrl',
|
||||
resolve: {
|
||||
itemType: function(){
|
||||
return $scope.itemType;
|
||||
},
|
||||
editItem: function(){
|
||||
return $scope.item;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.then(function (rel) {
|
||||
var r = angular.copy(rel);
|
||||
}, function (uploadedFiles) {
|
||||
angular.forEach(uploadedFiles, function(fn){
|
||||
$scope.item.addAttachment(fn);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@@ -0,0 +1,678 @@
|
||||
/*
|
||||
Setn log
|
||||
/wp/1/settings/GetSentItems?MessageID=6&SQL=Select+*+from+SentItems&LIMIT=10
|
||||
|
||||
Plain Log
|
||||
/wp/1/settings/Outbox/6/Log
|
||||
|
||||
*/
|
||||
|
||||
function EmailsCtrl($scope, $http, $stateParams, EmEmails, emAuth, synConn, emUtils, EmSentLog) {
|
||||
|
||||
$scope.emails = new EmEmails($scope, {
|
||||
showDeleted: $scope.subSection === 'trash'
|
||||
});
|
||||
|
||||
$scope.log = null;
|
||||
|
||||
$scope.activateEmail = function(eml) {
|
||||
$scope.log = null;
|
||||
|
||||
$scope.emails.activeItem = eml.ID;
|
||||
$scope.setEmail(eml);
|
||||
|
||||
if ( $scope.item && $scope.item.stopPolling ) {
|
||||
$scope.item.stopPolling();
|
||||
}
|
||||
|
||||
$scope.setItem(eml);
|
||||
|
||||
if ( eml.Status != 2 && eml.startPolling ) {
|
||||
eml.startPolling();
|
||||
}
|
||||
openPreview(eml);
|
||||
};
|
||||
|
||||
$scope.emlClick = function(eml) {
|
||||
window.location.hash = '#/workplaces/'+$scope.wpid+'/outbox/'+eml.ID+'/preview';
|
||||
};
|
||||
|
||||
$scope.startSending = function($scope, $http) {
|
||||
$http({ method: 'GET', url: '' })
|
||||
};
|
||||
|
||||
$scope.setPreview = function(){
|
||||
$scope.log = null;
|
||||
|
||||
$http({ method: 'GET', url: 'http://blog.dev/wp-admin/admin.php?page=newsman-templates&action=edit&id=16&tpl-source=1' })
|
||||
.success(function(data, status, headers, config) {
|
||||
|
||||
var zoomStyle = ['<style>',
|
||||
'html { ',
|
||||
'-ms-zoom: 0.65;',
|
||||
'-moz-transform:scale(0.65);',
|
||||
'-moz-tansform-origin: 0 0;',
|
||||
'-o-transform: scale(0.65);',
|
||||
'-o-transform-origin: 0 0;',
|
||||
'-webkit-transform: scale(0.65);',
|
||||
'-webkit-transform-origin: 0 0;',
|
||||
'}</style>'].join('');
|
||||
|
||||
data = data.replace('</head>', zoomStyle+'</head>');
|
||||
openPreview(data);
|
||||
// this callback will be called asynchronously
|
||||
// when the response is available
|
||||
})
|
||||
.error(function(data, status, headers, config) {
|
||||
// called asynchronously if an error occurs
|
||||
// or server returns response with an error status.
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewLog = function(emailId) {
|
||||
$scope.log = null;
|
||||
|
||||
var htmlWrapper = [
|
||||
'<html><head><style>',
|
||||
'pre {',
|
||||
'font-size: 12px;',
|
||||
'font-family: sans-serif;',
|
||||
'}',
|
||||
'</style></head><body><pre>{body}</pre></body></html>'].join('');
|
||||
|
||||
synConn
|
||||
.http({
|
||||
method: 'GET',
|
||||
uri: 'wp/{wpId}/settings/Outbox/'+emailId+'/Log'
|
||||
})
|
||||
.success(function(data){
|
||||
setIFrameContent(emUtils.supplant(htmlWrapper, { body: data }));
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.emDelete = function(eml) {
|
||||
var delFromTrash = $scope.subSection === 'trash';
|
||||
|
||||
if ( delFromTrash && !confirm('Are you sure you want to delete this email?') ) { return; }
|
||||
|
||||
eml.del(function(err){
|
||||
if ( err ) { console.log(err); return; }
|
||||
$scope.emails.del(eml);
|
||||
}, delFromTrash);
|
||||
};
|
||||
|
||||
$scope.viewSentLog = function(emailId, type) {
|
||||
$scope.log = new EmSentLog($scope, emailId, type);
|
||||
$scope.log.nextPage();
|
||||
}
|
||||
}
|
||||
|
||||
app.controller('EmailsItemDetailsCtrl', function($scope, $stateParams){
|
||||
|
||||
var subView = ( typeof $stateParams.subview === 'undefined' ) ? 'preview' : $stateParams.subview;
|
||||
|
||||
switch ( subView ) {
|
||||
case 'preview':
|
||||
$scope.emails.getEmail($stateParams.id, function(err, eml){
|
||||
if ( err ) { return console.error(err); }
|
||||
$scope.activateEmail(eml);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
$scope.viewLog($stateParams.id);
|
||||
break;
|
||||
|
||||
case 'processed':
|
||||
$scope.viewSentLog($stateParams.id, 'processed');
|
||||
break;
|
||||
|
||||
case 'sent':
|
||||
$scope.viewSentLog($stateParams.id, 'sent');
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
$scope.viewSentLog($stateParams.id, 'failed');
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.factory('EmEml', function($http, $q, synConn, emUtils, emAuth, $timeout, $interval){
|
||||
|
||||
var EmEml = function(data){
|
||||
var defaults = {
|
||||
AccountList: [],
|
||||
AttachList: [],
|
||||
Attempt: 0,
|
||||
Bounced: 0,
|
||||
CID: 0,
|
||||
CName: "",
|
||||
CharSet: "WINDOWS-1252",
|
||||
Complaint: 0,
|
||||
ConfirmRead: 0,
|
||||
CreateTime: 135125718208,
|
||||
Deleted: 0,
|
||||
Excluded: 0,
|
||||
Failed: 0,
|
||||
FinishTime: "",
|
||||
GroupList: [],
|
||||
Guid: '{'+emUtils.UUID()+'}',
|
||||
ID: 0,
|
||||
MailTo: "",
|
||||
MessageFormat: 0,
|
||||
Priority: 3,
|
||||
Processed: 2,
|
||||
ResumeTime: "",
|
||||
SendMode: 0,
|
||||
Sent: 2,
|
||||
StartTime: "",
|
||||
Status: 2, // 0- RedyForSent; 1 - InSent; 2- Sending Finished; 3 - Pending
|
||||
StatusMessage: "Finished [View Log]",
|
||||
Subject: "Pure Papers",
|
||||
Total: 0,
|
||||
Total_Clicks: 0,
|
||||
Total_Opens: 0,
|
||||
TrackMethod: 0,
|
||||
TrackName: "",
|
||||
Unique_Clicks: 0,
|
||||
Unique_Opens: 0,
|
||||
Unsubscribed: 0,
|
||||
UseAnalytics: 0,
|
||||
User: null
|
||||
};
|
||||
|
||||
angular.extend(this, defaults);
|
||||
if ( typeof data !== 'undefined' ) {
|
||||
angular.extend(this, data);
|
||||
}
|
||||
|
||||
this.CreateTime = emUtils.timeLogToDate(this.CreateTime);
|
||||
};
|
||||
|
||||
EmEml.status = {
|
||||
READY_FOR_SENT: 0,
|
||||
SENDING: 1,
|
||||
FINISHED: 2,
|
||||
PENDING: 3
|
||||
//0: RedyForSent; 1 - InSent; 2- Sending Finished; 3 - Pending
|
||||
};
|
||||
|
||||
EmEml.canBeSent = function(o) {
|
||||
var st = (typeof o === 'number') ? o : o.Status;
|
||||
var x =
|
||||
st == EmEml.status.FINISHED ||
|
||||
st == EmEml.status.READY_FOR_SENT;
|
||||
|
||||
return x;
|
||||
};
|
||||
|
||||
EmEml.canBeStopped = function(o) {
|
||||
var st = (typeof o === 'number') ? o : o.Status;
|
||||
var x =
|
||||
st == EmEml.status.PENDING ||
|
||||
st == EmEml.status.SENDING;
|
||||
return x;
|
||||
};
|
||||
|
||||
var p = EmEml.prototype;
|
||||
|
||||
p.getBaseURL = function() {
|
||||
return '/wps/outbox/'+emAuth.get('workplace').ID+'/'+this.Guid+'/';
|
||||
};
|
||||
|
||||
p.save = function(done) {
|
||||
var _this = this;
|
||||
done = done || function(){};
|
||||
|
||||
/*
|
||||
'/wp/1/settings/Drafts?session_signature=004888DF0000838811DF13D9',
|
||||
'POST',
|
||||
|
||||
'Content-Type: application/json; charset=UTF-8'
|
||||
|
||||
{
|
||||
"CharSet":"UTF-8",
|
||||
"Subject":"Test",
|
||||
"MailTo":"",
|
||||
"ModTime":135135803300,"UseAnalytics":false,"CID":0,"CName":"",
|
||||
"TrackMethod":0,"TrackName":"",
|
||||
"Priority":1,
|
||||
"ConfirmRead":false,"Deleted":false,"MessageFormat":0,"AttachList":"[]",
|
||||
"AccountList":"[\"1\"]",
|
||||
"GroupList":"[\"1\"]",
|
||||
"Guid":""
|
||||
}
|
||||
|
||||
//*/
|
||||
|
||||
//'id', 'MessageFormat', 'Guid', 'EmailSource', 'Name', 'Subject', 'AccountList', 'GroupList'
|
||||
|
||||
var obj = {
|
||||
'id': this.ID,
|
||||
'MessageFormat': 0, // 0 - html, 1 - plain
|
||||
'Guid': item.Guid,
|
||||
'EmailSource': emUtils.shrinkAssetURLs(this.EmailSource, this.getBaseURL()),
|
||||
'Subject': this.Subject,
|
||||
'AccountList': [],
|
||||
'GroupList': []
|
||||
};
|
||||
|
||||
synConn
|
||||
.http({
|
||||
method: 'POST',
|
||||
uri: 'wp/{wpId}/settings/SetOutboxMessage',
|
||||
data: obj
|
||||
})
|
||||
.success(function(data){
|
||||
_this.ID = data.ID;
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
p.setBody = function(body) {
|
||||
this.EmailSource = body;
|
||||
}
|
||||
|
||||
p.getBody = function() {
|
||||
var _this = this;
|
||||
if ( typeof this._data !== 'undefined' ) {
|
||||
return this.EmailSource;
|
||||
}
|
||||
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetOutboxMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
_this._data = data;
|
||||
|
||||
try {
|
||||
data.EmailSource = emUtils.unpackEmailSource(data.EmailSource);
|
||||
} catch(e) {
|
||||
data.EmailSource = 'error';
|
||||
defer.reject(e);
|
||||
}
|
||||
|
||||
|
||||
//data.BaseURL = emUtils.supplant('/wps/outbox/{wpid}/{guid}/',{ guid: data.Guid, wpid: emAuth.get('workplace').ID });
|
||||
_this.EmailSource = emUtils.expandAssetURLs(data.EmailSource, _this.getBaseURL());
|
||||
defer.resolve(_this.EmailSource);
|
||||
|
||||
//this.CreateTime = emUtils.timeLogToDate(this.CreateTime);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
p.reloadData = function(done) {
|
||||
done = done || function(){};
|
||||
var _this = this;
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetOutboxMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
_this._data = data;
|
||||
|
||||
try {
|
||||
data.EmailSource = emUtils.unpackEmailSource(data.EmailSource);
|
||||
} catch(e) {
|
||||
data.EmailSource = 'error';
|
||||
}
|
||||
|
||||
|
||||
//data.BaseURL = emUtils.supplant('/wps/outbox/{wpid}/{guid}/',{ guid: data.Guid, wpid: emAuth.get('workplace').ID });
|
||||
_this.EmailSource = emUtils.expandAssetURLs(data.EmailSource, _this.getBaseURL());
|
||||
|
||||
// refreshing the rest of data
|
||||
if ( typeof data !== 'undefined' ) {
|
||||
angular.extend(_this, data);
|
||||
}
|
||||
done(null, data);
|
||||
//this.CreateTime = emUtils.timeLogToDate(this.CreateTime);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
p.del = function(done, forever) {
|
||||
var req;
|
||||
|
||||
if ( forever ) {
|
||||
req = {
|
||||
method: 'DELETE',
|
||||
uri: 'wp/{wpId}/settings/outbox/'+this.ID
|
||||
};
|
||||
} else {
|
||||
req = {
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/outbox/'+this.ID,
|
||||
data: {
|
||||
deleted: 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
synConn
|
||||
.http(req)
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
||||
p.startPolling = function() {
|
||||
var _this = this;
|
||||
this.pollForUpdates();
|
||||
};
|
||||
|
||||
p.stopPolling = function() {
|
||||
if ( this.pollTO ) {
|
||||
$timeout.cancel(this.pollTO);
|
||||
}
|
||||
};
|
||||
|
||||
p.pollForUpdates = function(){
|
||||
var _this = this;
|
||||
_this.reloadData(function(err, data){
|
||||
if ( !err && data && typeof _this._oldStatus !== 'undefined' && _this._oldStatus !== data.Status ) {
|
||||
// if status changed - stop the polling
|
||||
delete _this._oldStatus;
|
||||
return;
|
||||
}
|
||||
_this.pollTO = $timeout(function(){
|
||||
_this.pollForUpdates();
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
p.startSending = function() {
|
||||
var _this = this;
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/SendMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
|
||||
_this.startPolling();
|
||||
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(data);
|
||||
});
|
||||
};
|
||||
|
||||
p.stopSending = function() {
|
||||
var _this = this;
|
||||
|
||||
this._oldStatus = this.Status;
|
||||
|
||||
// saving old status, then check in poll function for changes and stop
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/StopSending?ID='+this.ID })
|
||||
.success(function(data){
|
||||
|
||||
$timeout(function(){
|
||||
_this.reloadData();
|
||||
}, 3000);
|
||||
|
||||
// if ( typeof data !== 'undefined' ) {
|
||||
// angular.extend(this, data);
|
||||
// }
|
||||
|
||||
// this.CreateTime = emUtils.timeLogToDate(this.CreateTime);
|
||||
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
});
|
||||
// SendDraftMessage
|
||||
// param - ID
|
||||
// StopSending - ID
|
||||
// SendMessage - ID
|
||||
};
|
||||
|
||||
return EmEml;
|
||||
});
|
||||
|
||||
// Reddit constructor function to encapsulate HTTP and pagination logic
|
||||
app.factory('EmEmails', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmEml) {
|
||||
var EmEmails = function($scope, opts) {
|
||||
opts = opts || {};
|
||||
this.$scope = $scope;
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.after = '';
|
||||
this.noMoreData = false;
|
||||
this.options = {
|
||||
showDeleted: false
|
||||
};
|
||||
angular.extend(this.options, opts);
|
||||
this.m = new Emmi();
|
||||
};
|
||||
|
||||
EmEmails.prototype.getEmail = function(id, done) {
|
||||
var _this = this;
|
||||
|
||||
if ( !this.items.length ) {
|
||||
this.m.once('data', function(){
|
||||
_this.getEmail(id, done);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var found = false;
|
||||
angular.forEach(this.items, function(eml){
|
||||
if ( eml.ID == id ) {
|
||||
done(null, eml);
|
||||
found = true;
|
||||
return false; // breaking the loop
|
||||
}
|
||||
});
|
||||
if ( !found ) {
|
||||
done(new Error('Email with id '+id+' is not found.'));
|
||||
}
|
||||
};
|
||||
|
||||
EmEmails.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.noMoreData ) { return; }
|
||||
if ( that.busy ) return;
|
||||
|
||||
that.busy = true;
|
||||
|
||||
var sql = '*';
|
||||
|
||||
function resolveGroups(emails, done) {
|
||||
|
||||
var emailsToProcess = emails.length;
|
||||
|
||||
for (var i = 0; i < emails.length; i++) {
|
||||
|
||||
emGroups.resolve(emails[i].GroupList, function(err, resolved){
|
||||
if (err) { return console.log(err); }
|
||||
emails[this.i].resGroupList = resolved;
|
||||
|
||||
emAccounts.resolve(emails[this.i].AccountList, function(err, resolvedAccounts){
|
||||
if (err) { return console.log(err); }
|
||||
|
||||
emails[this.i].resAccountList = resolvedAccounts;
|
||||
|
||||
emailsToProcess -= 1;
|
||||
if ( emailsToProcess === 0 ) {
|
||||
done(emails);
|
||||
}
|
||||
|
||||
}.bind({ i: this.i }));
|
||||
|
||||
}.bind({ i: i }));
|
||||
}
|
||||
}
|
||||
|
||||
var start = this.items.length;
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/Outbox?Select=*&where=deleted='+(this.options.showDeleted ? '1' : '0')+'&STARTINDEX='+start+'&RESULTS=10&SORT=CreateTime&DIR=DESC' })
|
||||
.success(function(data){
|
||||
|
||||
// no more emails
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
} else {
|
||||
resolveGroups(data, function(emails){
|
||||
for (var i = 0; i < emails.length; i++) {
|
||||
that.items.push(new EmEml(emails[i]));
|
||||
}
|
||||
that.busy = false;
|
||||
that.m.emit('data');
|
||||
defer.resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
console.error(err)
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
EmEmails.prototype.del = function(eml) {
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
if ( this.items[i] === eml ) {
|
||||
this.items.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return EmEmails;
|
||||
});
|
||||
|
||||
app.factory('EmSentLog', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmEml) {
|
||||
var EmSentLog = function($scope, msgId, type) {
|
||||
this.$scope = $scope;
|
||||
this.type = type;
|
||||
this.msgId = msgId;
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.noMoreData = false;
|
||||
this.after = '';
|
||||
};
|
||||
|
||||
EmSentLog.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.busy || that.noMoreData ) return;
|
||||
that.busy = true;
|
||||
|
||||
function resolveGroups(emails, done) {
|
||||
|
||||
var emailsToProcess = emails.length;
|
||||
|
||||
for (var i = 0; i < emails.length; i++) {
|
||||
|
||||
emGroups.resolve(emails[i].GroupList, function(err, resolved){
|
||||
if (err) { throw err; }
|
||||
emails[this.i].GroupList = resolved;
|
||||
|
||||
emAccounts.resolve(emails[this.i].AccountList, function(err, resolvedAccounts){
|
||||
if (err) { throw err; }
|
||||
|
||||
emails[this.i].AccountList = resolvedAccounts;
|
||||
|
||||
emailsToProcess -= 1;
|
||||
if ( emailsToProcess === 0 ) {
|
||||
done(emails);
|
||||
}
|
||||
}.bind({ i: this.i }));
|
||||
}.bind({ i: i }));
|
||||
}
|
||||
}
|
||||
|
||||
var start = this.items.length;
|
||||
|
||||
/*
|
||||
$$hashKey: "01Q"
|
||||
Clicked: 0
|
||||
Email: "sales@cartridgecare.com"
|
||||
GroupName: "Import From SQL Server EasyMail"
|
||||
ID: 1
|
||||
LastResponse: "63"
|
||||
Log: "PFNlbmRSYXdFbWFpbFJlc3BvbnNlIHhtbG5zPSJodHRwOi8vc2VzLmFtYXpvbmF3cy5jb20vZG9jLzIwMTAtMTItMDEvIj4KICA8U2VuZFJhd0VtYWlsUmVzdWx0PgogICAgPE1lc3NhZ2VJZD4wMDAwMDE0MTNiN2RjY2E2LWExMTdhMjllLWIwYzAtNGI0NS05ZTRlLTQ2ZmE1MTlhMThkYy0wMDAwMDA8L01lc3NhZ2VJZD4KICA8L1NlbmRSYXdFbWFpbFJlc3VsdD4KICA8UmVzcG9uc2VNZXRhZGF0YT4KICAgIDxSZXF1ZXN0SWQ+ZjU4NzBmMmItMjFmNS0xMWUzLWEwNWEtMjc4Y2Q5NmI3ZGFmPC9SZXF1ZXN0SWQ+CiAgPC9SZXNwb25zZU1ldGFkYXRhPgo8L1NlbmRSYXdFbWFpbFJlc3BvbnNlPgoNCg0KODg5"
|
||||
Opened: 0
|
||||
RecipientName: "cartridgecare"
|
||||
SendDate: "2013-09-20T13:10:44"
|
||||
Status: 1
|
||||
*/
|
||||
var ipp = 25,
|
||||
sql = '';
|
||||
|
||||
switch (this.type) {
|
||||
case 'processed':
|
||||
sql = 'Select * from SentItems LIMIT '+start+','+ipp;
|
||||
break;
|
||||
case 'sent':
|
||||
sql = 'Select * from SentItems where Status = 1 LIMIT '+start+','+ipp;
|
||||
break;
|
||||
case 'failed':
|
||||
sql = 'Select * from SentItems where Status != 1 LIMIT '+start+','+ipp;
|
||||
break;
|
||||
}
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetSentItems?MessageID='+this.msgId+'&SQL='+sql.replace(/\s+/g, '+') })
|
||||
.success(function(data){
|
||||
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
data[i].SendDate = new Date(data[i].SendDate);
|
||||
switch ( data[i].Status ) {
|
||||
case 1:
|
||||
data[i].StatusText = 'Successfully delivered';
|
||||
break;
|
||||
case 2:
|
||||
data[i].StatusText = 'Temporary error. Sending attempt will be repeted in a minute';
|
||||
break;
|
||||
case 3:
|
||||
data[i].StatusText = 'Error';
|
||||
break;
|
||||
}
|
||||
that.items.push(data[i]);
|
||||
}
|
||||
that.busy = false;
|
||||
defer.resolve();
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
return EmSentLog;
|
||||
});
|
@@ -0,0 +1,454 @@
|
||||
function TemplatesCtrl($scope, $http, $location, $stateParams, $state, EmTemplates, emAuth, synConn, emUtils) {
|
||||
|
||||
var sub = $stateParams.folder || $scope.subSection;
|
||||
|
||||
$scope.setSub(sub);
|
||||
$scope.templates = new EmTemplates($scope, sub);
|
||||
|
||||
function expandAssetURLs(src, baseURL) {
|
||||
|
||||
function replacer(match, p1, p2, p3, p4) {
|
||||
var url = p3;
|
||||
|
||||
if ( url.match(/^\w+:\/\//) ) {
|
||||
return p1+url+p4;
|
||||
}
|
||||
|
||||
if ( baseURL[baseURL.length-1] !== '/' && url[0] !== '/' ) { baseURL += '/'; }
|
||||
|
||||
url = baseURL+url;
|
||||
return p1+url+p4;
|
||||
}
|
||||
|
||||
return src
|
||||
.replace(/(\b(?:src|url|background)=(\\\'|"))(.*?)(\2)/ig, replacer)
|
||||
.replace(/(\burl\(([\\\'"]{0,1}))(.*?)(\2\))/ig, replacer);
|
||||
}
|
||||
|
||||
$scope.tplClick = function(tpl) {
|
||||
$scope.templates.activeItem = tpl.ID;
|
||||
$scope.item = tpl;
|
||||
|
||||
var tpl = $scope.templates.getTemplate(tpl.ID);
|
||||
openPreview(tpl);
|
||||
};
|
||||
|
||||
$scope.tplDelete = function(e, tpl) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var delfromTrash = $scope.subSection === 'trash';
|
||||
if ( delfromTrash && !confirm('Are you sure you want to delete this template?') ) { return; }
|
||||
tpl.del(function(err){
|
||||
if ( err ) { console.log(err); return; }
|
||||
$scope.templates.del(tpl);
|
||||
}, delfromTrash);
|
||||
};
|
||||
|
||||
$scope.tplRestore = function(e, tpl) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
tpl.restore(function(err){
|
||||
if ( err ) { console.log(err); return; }
|
||||
$scope.templates.del(tpl);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.tplCreateMsg = function(e, tpl) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
synConn
|
||||
.http({
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/NewMessageFromTemplate?ID='+tpl.ID
|
||||
})
|
||||
.success(function(data){
|
||||
$location.path('/workplaces/'+emAuth.get('workplace').ID+'/drafts/'+data.ID+'/edit');
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.startSending = function($scope, $http) {
|
||||
$http({ method: 'GET', url: '' })
|
||||
};
|
||||
|
||||
$scope.setPreview = function(){
|
||||
|
||||
$http({ method: 'GET', url: 'http://blog.dev/wp-admin/admin.php?page=newsman-templates&action=edit&id=16&tpl-source=1' })
|
||||
.success(function(data, status, headers, config) {
|
||||
|
||||
var zoomStyle = ['<style>',
|
||||
'html { ',
|
||||
'-ms-zoom: 0.65;',
|
||||
'-moz-transform:scale(0.65);',
|
||||
'-moz-tansform-origin: 0 0;',
|
||||
'-o-transform: scale(0.65);',
|
||||
'-o-transform-origin: 0 0;',
|
||||
'-webkit-transform: scale(0.65);',
|
||||
'-webkit-transform-origin: 0 0;',
|
||||
'}</style>'].join('');
|
||||
|
||||
data = data.replace('</head>', zoomStyle+'</head>');
|
||||
openPreview(data);
|
||||
// this callback will be called asynchronously
|
||||
// when the response is available
|
||||
})
|
||||
.error(function(data, status, headers, config) {
|
||||
// called asynchronously if an error occurs
|
||||
// or server returns response with an error status.
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
app.controller('TemplateEditorCtrl', function($scope, $controller, $modal, $stateParams, emAuth, synConn, emUtils, emCategories, EmTemplates, groups){
|
||||
$controller('CommonEditorController', { $scope: $scope });
|
||||
|
||||
$scope.stateParams = $stateParams;
|
||||
|
||||
var gotScopeItem;
|
||||
|
||||
$scope.itemType = '';
|
||||
|
||||
$scope.groups = groups;
|
||||
$scope.to = [];
|
||||
|
||||
$scope.$watch('item', function(newVal){
|
||||
if ( typeof newVal !== 'undefined' && !newVal.then ) {
|
||||
$scope.to = $scope.item.GroupList;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('to', function(newVal){
|
||||
if ( $scope.item ) {
|
||||
$scope.item.GroupList = newVal
|
||||
}
|
||||
});
|
||||
|
||||
$scope.enableTracking = false;
|
||||
|
||||
$scope.$watch('item', function(item){
|
||||
if ( typeof item !== 'undefined' && !item.then ) {
|
||||
$scope.enableTracking = item.TrackMethod > 0;
|
||||
}
|
||||
});
|
||||
|
||||
if ( $stateParams.template ) {
|
||||
$scope.itemType = 'template';
|
||||
|
||||
$scope.setItem( (new EmTemplates()).getTemplate($stateParams.template) );
|
||||
|
||||
var p = { root: 'templates' };
|
||||
|
||||
gotScopeItem = function(tpl) {
|
||||
//p.name = drf.Subject;
|
||||
p.item = tpl;
|
||||
$scope.setItemParams(p);
|
||||
//$scope.item = tpl;
|
||||
$scope.setItem(tpl);
|
||||
window.tpl = tpl;
|
||||
};
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
if ( $scope.item.then ) {
|
||||
$scope.item.then(
|
||||
gotScopeItem,
|
||||
function(err){
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
gotScopeItem($scope.item);
|
||||
}
|
||||
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.item.save(function(err){
|
||||
$scope.alert({
|
||||
type:'success',
|
||||
msg: 'Successfully saved'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
var TemplateEditorResolve = {
|
||||
groups: function($q, synConn) {
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({
|
||||
uri: 'wp/{wpId}/settings/Groups?Select=ID,GroupName,GroupKind&WHERE=Hidden=0'
|
||||
})
|
||||
.success(function(data){
|
||||
angular.forEach(data, function(g){
|
||||
g.type = (g.GroupKind == 1) ? 'My Groups' : 'My Databases';
|
||||
});
|
||||
defer.resolve(data);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
};
|
||||
|
||||
app.factory('EmTpl', function($http, $q, synConn, emUtils, emAuth){
|
||||
var EmTpl = function(data){
|
||||
|
||||
var defaults = {
|
||||
AttachList: "[]",
|
||||
CategoryID: 0,
|
||||
CharSet: "UTF-8",
|
||||
GroupList: [],
|
||||
Guid: "{"+emUtils.UUID()+"}",
|
||||
//ID: 17,
|
||||
MessageFormat: 0,
|
||||
ModTime: (new Date()).getTime(),
|
||||
//StoreID: 6,
|
||||
Subject: "No Subject",
|
||||
TemplateName: "Unnamed",
|
||||
};
|
||||
|
||||
angular.extend(this, defaults);
|
||||
if ( typeof data !== 'undefined' ) {
|
||||
angular.extend(this, data);
|
||||
}
|
||||
this.ModTime = emUtils.timeLogToDate(this.ModTime);
|
||||
|
||||
this.GroupList = this._map(this.GroupList, function(itm){ return (typeof itm === 'string') ? parseInt(itm, 10) : itm; });
|
||||
};
|
||||
var p = EmTpl.prototype;
|
||||
|
||||
p.getBaseURL = function() {
|
||||
return '/wps/templates/'+emAuth.get('workplace').ID+'/'+this.Guid+'/';
|
||||
};
|
||||
|
||||
p._map = function(arr, mapper) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
arr[i] = mapper(arr[i], i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
p.save = function(done) {
|
||||
done = done || function(){};
|
||||
|
||||
var obj = {
|
||||
ID: this.ID,
|
||||
CharSet : 'UTF-8',
|
||||
CategoryID: 0,
|
||||
TemplateName: this.TemplateName,
|
||||
Subject: this.Subject,
|
||||
MessageFormat: 0, // 0 - html, 1 - plain
|
||||
GroupList: this._map(this.GroupList, function(itm){ return itm+''; }), // ~
|
||||
AttachList: this.AttachList,
|
||||
Guid: item.Guid
|
||||
};
|
||||
|
||||
if ( typeof this._data !== 'undefined' ) {
|
||||
obj.EmailSource = emUtils.shrinkAssetURLs(this.EmailSource, this.getBaseURL());
|
||||
}
|
||||
|
||||
synConn
|
||||
.http({
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/SetTemplateMessage',
|
||||
data: obj
|
||||
})
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
p.setBody = function(body) {
|
||||
this.EmailSource = body;
|
||||
}
|
||||
|
||||
p.getBody = function() {
|
||||
var _this = this;
|
||||
if ( typeof this._data !== 'undefined' ) {
|
||||
return this.EmailSource;
|
||||
}
|
||||
|
||||
var defer = $q.defer();
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetTemplateMessage?ID='+this.ID })
|
||||
.success(function(data){
|
||||
_this._data = data;
|
||||
_this.Guid = data.Guid;
|
||||
data.EmailSource = emUtils.unpackEmailSource(data.EmailSource);
|
||||
_this.EmailSource = emUtils.expandAssetURLs(data.EmailSource, _this.getBaseURL());
|
||||
defer.resolve(_this.EmailSource);
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
p.del = function(done, forever) {
|
||||
var req;
|
||||
if ( forever ) {
|
||||
req = {
|
||||
method: 'DELETE',
|
||||
uri: 'wp/{wpId}/settings/templates/'+this.ID
|
||||
};
|
||||
} else {
|
||||
req = {
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/templates/'+this.ID,
|
||||
data: {
|
||||
CategoryID: -1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
synConn
|
||||
.http(req)
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
||||
p.restore = function(done) {
|
||||
synConn
|
||||
.http({
|
||||
method: 'PUT',
|
||||
uri: 'wp/{wpId}/settings/templates/'+this.ID,
|
||||
data: {
|
||||
CategoryID: 0
|
||||
}
|
||||
})
|
||||
.success(function(data){
|
||||
done(null);
|
||||
})
|
||||
.error(function(err){
|
||||
console.error(err);
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
return EmTpl;
|
||||
});
|
||||
|
||||
app.factory('EmTemplates', function($http, $q, emAuth, synConn, emGroups, emAccounts, EmTpl) {
|
||||
var EmTemplates = function($scope, cat) {
|
||||
this.$scope = $scope;
|
||||
this.items = [];
|
||||
this.busy = false;
|
||||
this.noMoreData = false;
|
||||
this.after = '';
|
||||
this.category = cat ? cat : 0;
|
||||
|
||||
if ( this.category === 'trash' ) {
|
||||
this.category = -1;
|
||||
}
|
||||
};
|
||||
|
||||
function resolveGroups(templates, done) {
|
||||
var templatesToProcess = templates.length;
|
||||
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
emGroups.resolve(templates[i].GroupList, function(err, resolved){
|
||||
if (err) { throw err; }
|
||||
templates[this.i].GroupList = resolved;
|
||||
templatesToProcess -= 1;
|
||||
if ( templatesToProcess === 0 ) {
|
||||
done(templates);
|
||||
}
|
||||
}.bind({ i: i }));
|
||||
}
|
||||
}
|
||||
|
||||
EmTemplates.prototype.getTemplate = function(id) {
|
||||
|
||||
var tpl;
|
||||
|
||||
if ( this.items ) {
|
||||
angular.forEach(this.items, function(t, i){
|
||||
if ( t.ID == id ) {
|
||||
tpl = t;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if ( tpl ) {
|
||||
return tpl;
|
||||
}
|
||||
}
|
||||
|
||||
var defer = $q.defer();
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/GetTemplateMessage?ID='+id })
|
||||
.success(function(data){
|
||||
defer.resolve(new EmTpl(data));
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
EmTemplates.prototype.nextPage = function() {
|
||||
var that = this,
|
||||
defer = $q.defer();
|
||||
|
||||
if ( that.busy ) return;
|
||||
that.busy = true;
|
||||
|
||||
var sql = '*';
|
||||
var start = this.items.length;
|
||||
|
||||
synConn
|
||||
.http({ uri: 'wp/{wpId}/settings/Templates?Select=*&where=CategoryID='+that.category+'&STARTINDEX='+start+'&RESULTS=10&SORT=ModTime&DIR=DESC' })
|
||||
.success(function(data){
|
||||
if ( typeof data.fieldCount !== 'undefined' ) {
|
||||
// no more templates
|
||||
that.busy = false;
|
||||
that.noMoreData = true;
|
||||
} else {
|
||||
resolveGroups(data, function(templates){
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
that.items.push(new EmTpl(templates[i]));
|
||||
}
|
||||
that.busy = false;
|
||||
defer.resolve(data);
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
.error(function(err){
|
||||
defer.reject(err);
|
||||
console.error(err)
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
EmTemplates.prototype.del = function(tpl) {
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
if ( this.items[i].id == tpl.id ) {
|
||||
this.items.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return EmTemplates;
|
||||
});
|
@@ -0,0 +1,64 @@
|
||||
var ckeditorBasePath = CKEDITOR.basePath.substr(0, CKEDITOR.basePath.indexOf('ckeditor/'));
|
||||
var customPluginsRoot = ckeditorBasePath + 'custom_plugins/';
|
||||
|
||||
CKEDITOR.plugins.addExternal('resizewithwindow', customPluginsRoot+'resizewithwindow/plugin.js', '');
|
||||
CKEDITOR.plugins.addExternal('iframedialog', customPluginsRoot+'iframedialog/plugin.js', '');
|
||||
CKEDITOR.plugins.addExternal('em_image_gallery', customPluginsRoot+'em_image_gallery/plugin.js', '');
|
||||
|
||||
CKEDITOR.editorConfig = function( config ) {
|
||||
// Define changes to default configuration here.
|
||||
// For the complete reference:
|
||||
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
|
||||
|
||||
// %REMOVE_START%
|
||||
//The configuration options below are needed when running CKEditor from source files.
|
||||
//config.plugins = 'dialogui,dialog,about,a11yhelp,basicstyles,blockquote,clipboard,panel,floatpanel,menu,contextmenu,resize,button,toolbar,elementspath,list,indent,enterkey,entities,popup,filebrowser,floatingspace,listblock,richcombo,format,htmlwriter,horizontalrule,wysiwygarea,image,fakeobjects,link,magicline,maximize,pastetext,pastefromword,removeformat,sourcearea,specialchar,menubutton,scayt,stylescombo,tab,table,tabletools,undo,wsc,panelbutton,colorbutton,font,justify,liststyle';
|
||||
//config.skin = 'moono';
|
||||
// %REMOVE_END%
|
||||
|
||||
|
||||
config.fullPage = true;
|
||||
config.allowedContent = true;
|
||||
|
||||
config.entities = false;
|
||||
config.basicEntities = true;
|
||||
//config.entities_latin = false;
|
||||
|
||||
config.resize_enabled = false;
|
||||
|
||||
config.extraPlugins = 'resizewithwindow,iframedialog,em_image_gallery';
|
||||
|
||||
config.enterMode = CKEDITOR.ENTER_BR;
|
||||
config.shiftEnterMode = CKEDITOR.ENTER_P;
|
||||
config.fillEmptyBlocks = false;
|
||||
|
||||
config.codemirror = {
|
||||
// showSearchButton: true,
|
||||
showCommentButton: false,
|
||||
showUncommentButton: false
|
||||
};
|
||||
|
||||
// The toolbar groups arrangement, optimized for two toolbar rows.
|
||||
config.toolbarGroups = [
|
||||
{ name: 'document', groups: [ 'mode', 'document' ] },
|
||||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
|
||||
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
|
||||
//'/',
|
||||
{ name: 'styles' },
|
||||
{ name: 'links' },
|
||||
'/',
|
||||
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
|
||||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ] },
|
||||
{ name: 'colors' },
|
||||
{ name: 'tools' },
|
||||
{ name: 'document', groups: [ 'document', 'doctools' ] },
|
||||
{ name: 'emBar' },
|
||||
{ name: 'insert' },
|
||||
{ name: 'about' },
|
||||
{ name: 'others' }
|
||||
];
|
||||
|
||||
// Remove some buttons, provided by the standard plugins, which we don't
|
||||
// need to have in the Standard(s) toolbar.
|
||||
config.removeButtons = 'Subscript,Superscript,Save,Styles';
|
||||
};
|
@@ -0,0 +1,210 @@
|
||||
angular.module('em-utils', [], function($provide) {
|
||||
|
||||
var makeCRCTable = function(){
|
||||
var c;
|
||||
var crcTable = [];
|
||||
for(var n =0; n < 256; n++){
|
||||
c = n;
|
||||
for(var k =0; k < 8; k++){
|
||||
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
}
|
||||
crcTable[n] = c;
|
||||
}
|
||||
return crcTable;
|
||||
}
|
||||
var crcTable = makeCRCTable();
|
||||
|
||||
var crc32 = function(str, init) {
|
||||
var crc = (typeof init === 'undefined') ? 0 ^ (-1) : init^0xFFFFFFFF;
|
||||
//var crc = 0 ^ (-1);
|
||||
|
||||
for (var i = 0; i < str.length; i++ ) {
|
||||
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
|
||||
}
|
||||
|
||||
return (crc ^ (-1)) >>> 0;
|
||||
};
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* SHA-256 implementation in JavaScript | (c) Chris Veness 2002-2010 | www.movable-type.co.uk */
|
||||
/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
|
||||
/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
var Sha256 = {}; // Sha256 namespace
|
||||
|
||||
/**
|
||||
* Generates SHA-256 hash of string
|
||||
*
|
||||
* @param {String} msg String to be hashed
|
||||
* @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash
|
||||
* @returns {String} Hash of msg as hex character string
|
||||
*/
|
||||
Sha256.hash = function(msg, utf8encode) {
|
||||
utf8encode = (typeof utf8encode == 'undefined') ? true : utf8encode;
|
||||
|
||||
// convert string to UTF-8, as SHA only deals with byte-streams
|
||||
if (utf8encode) msg = Utf8.encode(msg);
|
||||
|
||||
// constants [§4.2.2]
|
||||
var K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
|
||||
// initial hash value [§5.3.1]
|
||||
var H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
||||
|
||||
// PREPROCESSING
|
||||
|
||||
msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
|
||||
|
||||
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
|
||||
var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + '1' + appended length
|
||||
var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
|
||||
var M = new Array(N);
|
||||
|
||||
for (var i=0; i<N; i++) {
|
||||
M[i] = new Array(16);
|
||||
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
|
||||
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
|
||||
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
|
||||
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
|
||||
}
|
||||
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
|
||||
// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
|
||||
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
|
||||
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
|
||||
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
|
||||
|
||||
|
||||
// HASH COMPUTATION [§6.1.2]
|
||||
|
||||
var W = new Array(64); var a, b, c, d, e, f, g, h;
|
||||
for (var i=0; i<N; i++) {
|
||||
|
||||
// 1 - prepare message schedule 'W'
|
||||
for (var t=0; t<16; t++) W[t] = M[i][t];
|
||||
for (var t=16; t<64; t++) W[t] = (Sha256.sigma1(W[t-2]) + W[t-7] + Sha256.sigma0(W[t-15]) + W[t-16]) & 0xffffffff;
|
||||
|
||||
// 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
|
||||
a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];
|
||||
|
||||
// 3 - main loop (note 'addition modulo 2^32')
|
||||
for (var t=0; t<64; t++) {
|
||||
var T1 = h + Sha256.Sigma1(e) + Sha256.Ch(e, f, g) + K[t] + W[t];
|
||||
var T2 = Sha256.Sigma0(a) + Sha256.Maj(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = (d + T1) & 0xffffffff;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = (T1 + T2) & 0xffffffff;
|
||||
}
|
||||
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
|
||||
H[0] = (H[0]+a) & 0xffffffff;
|
||||
H[1] = (H[1]+b) & 0xffffffff;
|
||||
H[2] = (H[2]+c) & 0xffffffff;
|
||||
H[3] = (H[3]+d) & 0xffffffff;
|
||||
H[4] = (H[4]+e) & 0xffffffff;
|
||||
H[5] = (H[5]+f) & 0xffffffff;
|
||||
H[6] = (H[6]+g) & 0xffffffff;
|
||||
H[7] = (H[7]+h) & 0xffffffff;
|
||||
}
|
||||
|
||||
return Sha256.toHexStr(H[0]) + Sha256.toHexStr(H[1]) + Sha256.toHexStr(H[2]) + Sha256.toHexStr(H[3]) +
|
||||
Sha256.toHexStr(H[4]) + Sha256.toHexStr(H[5]) + Sha256.toHexStr(H[6]) + Sha256.toHexStr(H[7]);
|
||||
}
|
||||
|
||||
Sha256.ROTR = function(n, x) { return (x >>> n) | (x << (32-n)); }
|
||||
Sha256.Sigma0 = function(x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); }
|
||||
Sha256.Sigma1 = function(x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); }
|
||||
Sha256.sigma0 = function(x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x>>>3); }
|
||||
Sha256.sigma1 = function(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); }
|
||||
Sha256.Ch = function(x, y, z) { return (x & y) ^ (~x & z); }
|
||||
Sha256.Maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }
|
||||
|
||||
//
|
||||
// hexadecimal representation of a number
|
||||
// (note toString(16) is implementation-dependant, and
|
||||
// in IE returns signed numbers when used on full words)
|
||||
//
|
||||
Sha256.toHexStr = function(n) {
|
||||
var s="", v;
|
||||
for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple */
|
||||
/* single-byte character encoding (c) Chris Veness 2002-2010 */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
var Utf8 = {}; // Utf8 namespace
|
||||
|
||||
/**
|
||||
* Encode multi-byte Unicode string into utf-8 multiple single-byte characters
|
||||
* (BMP / basic multilingual plane only)
|
||||
*
|
||||
* Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
|
||||
*
|
||||
* @param {String} strUni Unicode string to be encoded as UTF-8
|
||||
* @returns {String} encoded string
|
||||
*/
|
||||
Utf8.encode = function(strUni) {
|
||||
// use regular expressions & String.replace callback function for better efficiency
|
||||
// than procedural approaches
|
||||
var strUtf = strUni.replace(
|
||||
/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
|
||||
function(c) {
|
||||
var cc = c.charCodeAt(0);
|
||||
return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
|
||||
);
|
||||
strUtf = strUtf.replace(
|
||||
/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
|
||||
function(c) {
|
||||
var cc = c.charCodeAt(0);
|
||||
return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
|
||||
);
|
||||
return strUtf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode utf-8 encoded string back into multi-byte Unicode characters
|
||||
*
|
||||
* @param {String} strUtf UTF-8 string to be decoded back to Unicode
|
||||
* @returns {String} decoded string
|
||||
*/
|
||||
Utf8.decode = function(strUtf) {
|
||||
// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
|
||||
var strUni = strUtf.replace(
|
||||
/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
|
||||
function(c) { // (note parentheses for precence)
|
||||
var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
|
||||
return String.fromCharCode(cc); }
|
||||
);
|
||||
strUni = strUni.replace(
|
||||
/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
|
||||
function(c) { // (note parentheses for precence)
|
||||
var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
|
||||
return String.fromCharCode(cc); }
|
||||
);
|
||||
return strUni;
|
||||
}
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
$provide.factory('crc32', function() {
|
||||
return crc32;
|
||||
});
|
||||
|
||||
$provide.factory('sha256', function() {
|
||||
return Sha256;
|
||||
});
|
||||
});
|
@@ -0,0 +1,492 @@
|
||||
angular.module('syn-auth', ['em-utils'], function($provide) {
|
||||
|
||||
// the number of login retries on 403 before logout
|
||||
var MAX_RECONNECT_COUNT = 5;
|
||||
|
||||
$provide.factory('SynAuth', function(crc32, sha256) {
|
||||
|
||||
function SynAuth(host, port) {
|
||||
|
||||
var defaults = {
|
||||
host: '',
|
||||
defaultPort: '888',
|
||||
|
||||
User : "",
|
||||
fRoot : "",
|
||||
fSessionID : 0,
|
||||
fSessionIDHexa8 : "",
|
||||
fSessionPrivateKey : 0,
|
||||
fSessionTickCountOffset : 0,
|
||||
fLastSessionTickCount : 0,
|
||||
|
||||
PasswordHashHexa : "",
|
||||
fServerTimeStampOffset : 0,
|
||||
fcallBack : null,
|
||||
ffailCallBack : null
|
||||
}; // SynAuth namespace
|
||||
|
||||
this.connectionReady = false;
|
||||
this.readyCallbacks = [];
|
||||
|
||||
for ( var p in defaults ) {
|
||||
this[p] = defaults[p];
|
||||
}
|
||||
|
||||
if ( host ) {
|
||||
this.setHost(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
var sp = SynAuth.prototype;
|
||||
|
||||
sp.ready = function(cb) {
|
||||
if ( this.connectionReady ) {
|
||||
cb(this);
|
||||
} else {
|
||||
this.readyCallbacks.push(cb);
|
||||
}
|
||||
};
|
||||
|
||||
sp.fireReadyCbs = function() {
|
||||
var _this = this;
|
||||
angular.forEach(this.readyCallbacks, function(cb){
|
||||
cb(_this);
|
||||
});
|
||||
};
|
||||
|
||||
sp.wrap = function(method){
|
||||
var that = this;
|
||||
return function(){
|
||||
method.apply(that, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
sp.setHost = function(host, port) {
|
||||
if ( !host.match(/\:\d+$/) ) {
|
||||
host += ':'+(port || this.defaultPort);
|
||||
}
|
||||
this.host = host;
|
||||
};
|
||||
|
||||
sp.LogIn = function (root, username, password, callback, failCallback){
|
||||
this.fRoot = root;
|
||||
this.User = username;
|
||||
this.PasswordHashHexa = sha256.hash("salt"+password);
|
||||
if (callback) {this.fcallBack = callback;}
|
||||
if (failCallback) {this.ffailCallback = failCallback;}
|
||||
$.get(this.host+"/"+root+"/TimeStamp", this.wrap(this.gotTimeStamp));
|
||||
}
|
||||
|
||||
sp.LogInAgain = function(callback){ //after timeout error for silent re-login
|
||||
this.fSessionID = 0;
|
||||
this.fSessionIDHexa8 = "";
|
||||
this.fSessionPrivateKey = 0;
|
||||
if (callback) {this.fcallBack = callback;} else {this.fcallBack = null;}
|
||||
$.get(this.host+"/"+this.fRoot+"/TimeStamp", this.wrap(this.gotTimeStamp));
|
||||
}
|
||||
|
||||
sp.gotTimeStamp = function (timestamp) {
|
||||
var s = '', d = new Date(), clientTime = '';
|
||||
timestamp = parseInt(timestamp, 10);
|
||||
s = d.getFullYear().toString(2);
|
||||
while(s.length < 13) { s = '0'+s;}
|
||||
clientTime = s;
|
||||
s = d.getMonth().toString(2);
|
||||
while(s.length < 4) { s = '0'+s;}
|
||||
clientTime = clientTime +s;
|
||||
s = (d.getDate()-1).toString(2);
|
||||
while(s.length < 5) { s = '0'+s;}
|
||||
clientTime = clientTime +s;
|
||||
s = d.getHours().toString(2);
|
||||
while(s.length < 5) { s = '0'+s;}
|
||||
clientTime = clientTime +s;
|
||||
s = d.getMinutes().toString(2);
|
||||
while(s.length < 6) { s = '0'+s;}
|
||||
clientTime = clientTime +s;
|
||||
s = d.getSeconds().toString(2);
|
||||
while(s.length < 6) { s = '0'+s;}
|
||||
clientTime = clientTime +s;
|
||||
|
||||
this.fServerTimeStampOffset = (timestamp - Math.floor(d.getTime()/10));
|
||||
$.get(this.host+"/"+this.fRoot+"/auth?UserName="+this.User, this.wrap(this.gotNonce));
|
||||
}
|
||||
|
||||
sp.gotNonce = function (aNonce){
|
||||
var that = this;
|
||||
//create client nonce
|
||||
var aClientNonce = "", s = "", d = new Date();
|
||||
aClientNonce = d.getFullYear().toString();
|
||||
s = d.getMonth().toString();
|
||||
if (s.length === 1) { s = '0'+s;}
|
||||
aClientNonce = aClientNonce + '-' + s;
|
||||
s = d.getDate().toString();
|
||||
if (s.length === 1) { s = '0'+s;}
|
||||
aClientNonce = aClientNonce + '-' + s + ' ';
|
||||
s = d.getHours().toString();
|
||||
if (s.length === 1) { s = '0'+s;}
|
||||
aClientNonce = aClientNonce + s;
|
||||
s = d.getMinutes().toString();
|
||||
if (s.length === 1) { s = '0'+s;}
|
||||
aClientNonce = aClientNonce + ':' + s;
|
||||
s = d.getSeconds().toString();
|
||||
if (s.length === 1) { s = '0'+s;}
|
||||
aClientNonce = aClientNonce + ':' + s;
|
||||
aClientNonce = sha256.hash(aClientNonce);
|
||||
s = this.host+"/"+ this.fRoot+"/auth?UserName="+this.User+"&Password=" +
|
||||
sha256.hash(this.fRoot+aNonce.result+aClientNonce+this.User+this.PasswordHashHexa )+
|
||||
"&ClientNonce="+aClientNonce;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: s,
|
||||
success: function(){ that.gotSession.apply(that, arguments); },
|
||||
error: function(){ that.ffailCallback.apply(that, arguments); }
|
||||
});
|
||||
};
|
||||
|
||||
sp.gotSession = function (aSessionKey){
|
||||
var sessArr = aSessionKey.result.split('+');
|
||||
|
||||
this.fSessionID = parseInt(sessArr[0], 10);
|
||||
this.fSessionIDHexa8 = this.fSessionID.toString(16);
|
||||
|
||||
while ( this.fSessionIDHexa8.length < 8 ) { this.fSessionIDHexa8 = '0'+this.fSessionIDHexa8; }
|
||||
|
||||
this.fSessionPrivateKey = crc32(this.PasswordHashHexa, crc32(aSessionKey.result, 0));
|
||||
|
||||
if (this.fcallBack != null) {
|
||||
this.connectionReady = true;
|
||||
this.fireReadyCbs();
|
||||
this.fcallBack();
|
||||
}
|
||||
}
|
||||
|
||||
sp.SessionSign = function (url) {
|
||||
var Tix, Nonce, s, ss;
|
||||
Tix = Date.now(); // # of ms since Epoch
|
||||
|
||||
if ( Tix <= this.fLastSessionTickCount ) {
|
||||
this.fLastSessionTickCount += 1;
|
||||
} else {
|
||||
this.fLastSessionTickCount = Tix;
|
||||
}
|
||||
Nonce = Tix.toString(16);
|
||||
|
||||
while ( Nonce.length < 8 ) { Nonce = '0'+Nonce; }
|
||||
if ( Nonce.length > 8 ) { Nonce = Nonce.slice(Nonce.length-8) }
|
||||
|
||||
ss = crc32(url, crc32(Nonce, this.fSessionPrivateKey)).toString(16);
|
||||
|
||||
while ( ss.length < 8 ) { ss = '0'+ss; }
|
||||
|
||||
s = url.indexOf("?") == -1 ? url+'?session_signature=' : url+'&session_signature=';
|
||||
|
||||
return s + this.fSessionIDHexa8 + Nonce + ss;
|
||||
}
|
||||
|
||||
sp.getURL = function(uri) {
|
||||
return this.host+'/'+this.SessionSign(uri);
|
||||
}
|
||||
|
||||
sp.Logout = function (callback) {
|
||||
if (this.fSessionID == 0) {if (callback){callback();}} else {
|
||||
$.get(this.host+"/"+this.fRoot+"/auth?UserName="+this.User+"&Session="+this.fSessionID, callback);
|
||||
this.fRoot = '';
|
||||
this.User = '';
|
||||
this.fSessionID = 0;
|
||||
this.fSessionIDHexa8 = "";
|
||||
this.fSessionPrivateKey = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return SynAuth;
|
||||
});
|
||||
|
||||
|
||||
// wps
|
||||
// wp/%/settings
|
||||
// wp/%/group
|
||||
// wp/%/bounce
|
||||
|
||||
// infinitypropertycomau
|
||||
|
||||
// garethinfinitypropertycomau
|
||||
|
||||
// bernieinfinitypropertycomau
|
||||
|
||||
var forEach = angular.forEach;
|
||||
|
||||
function isArray(value) {
|
||||
return Object.prototype.toString.apply(value) == '[object Array]';
|
||||
}
|
||||
|
||||
function isObject(value){return value != null && typeof value == 'object';}
|
||||
|
||||
function sortedKeys(obj) {
|
||||
var keys = [];
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
return keys.sort();
|
||||
}
|
||||
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
var keys = sortedKeys(obj);
|
||||
for ( var i = 0; i < keys.length; i++) {
|
||||
iterator.call(context, obj[keys[i]], keys[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
return encodeURIComponent(val).
|
||||
replace(/%40/gi, '@').
|
||||
replace(/%3A/gi, ':').
|
||||
replace(/%24/g, '$').
|
||||
replace(/%2C/gi, ',').
|
||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
|
||||
function buildQueryString(params) {
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value == null || value == undefined) return;
|
||||
if (!isArray(value)) value = [value];
|
||||
|
||||
forEach(value, function(v) {
|
||||
if (isObject(v)) {
|
||||
v = toJson(v);
|
||||
}
|
||||
parts.push(encodeUriQuery(key) + '=' +
|
||||
encodeUriQuery(v));
|
||||
});
|
||||
});
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
function buildUrl(url, params) {
|
||||
if (!params) return url;
|
||||
var qs = buildQueryString(params);
|
||||
|
||||
return url + ((url.indexOf('?') == -1) ? '?' : '&') + qs;
|
||||
}
|
||||
|
||||
$provide.factory('synConn', function(SynAuth, emAuth, $http, $q, emsLoadingBar) {
|
||||
var synConn = {
|
||||
opts: {
|
||||
host: emAuth.get('host'),
|
||||
port: emAuth.get('port'),
|
||||
email: emAuth.get('email'),
|
||||
password: emAuth.get('password'),
|
||||
secure: emAuth.get('secure')
|
||||
}
|
||||
};
|
||||
|
||||
var roots = [
|
||||
'wps',
|
||||
'wp/\\d+/settings',
|
||||
'wp/\\d+/group',
|
||||
'wp/\\d+/bounce'
|
||||
];
|
||||
|
||||
var connCache = {};
|
||||
|
||||
function setOpts(o) {
|
||||
synConn.opts = o;
|
||||
if ( !o.host ) { throw new Exception('[SynConn] setOpts. "host" option is not defined.'); }
|
||||
if ( !o.email ) { throw new Exception('[SynConn] setOpts. "email" option is not defined.'); }
|
||||
if ( !o.password ) { throw new Exception('[SynConn] setOpts. "password" option is not defined.'); }
|
||||
o.port = o.port || '888';
|
||||
}
|
||||
|
||||
function supplant (str, o) {
|
||||
return str.replace(
|
||||
/\{([^{}]*)\}/g,
|
||||
function (a, b) {
|
||||
var r = o[b];
|
||||
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getBaseURL() {
|
||||
var pr = synConn.opts.secure ? 'https' : 'http';
|
||||
return pr+'://'+synConn.opts.host+':'+synConn.opts.port+'/';
|
||||
}
|
||||
|
||||
function createNewSynAuth(root, done) {
|
||||
var pr = synConn.opts.secure ? 'https' : 'http';
|
||||
//return { SynAuth: 'dummy'+(new Date()).getTime() };
|
||||
|
||||
var sa = new SynAuth(pr+'://'+synConn.opts.host, synConn.opts.port);
|
||||
|
||||
sa.LogIn(root, synConn.opts.email, synConn.opts.password, function(){
|
||||
done(null, sa);
|
||||
}, function(xhr, status, msg){
|
||||
// failback
|
||||
var err = new Error(msg);
|
||||
err.status = xhr.status;
|
||||
done(err);
|
||||
});
|
||||
|
||||
return sa;
|
||||
}
|
||||
|
||||
function getConnectionFromURi(uri, done) {
|
||||
var found = false;
|
||||
// looking for cached connection
|
||||
for ( var root in connCache ) {
|
||||
if ( uri.indexOf(root) === 0 ) {
|
||||
return connCache[root].ready(function(conn){
|
||||
done(null, conn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// creating new connection
|
||||
for (var r, res, i = 0; i < roots.length; i++) {
|
||||
if ( res = (new RegExp(roots[i],'i')).exec(uri) ) {
|
||||
r = res[0]; // matched root uri, like wp/1/settings
|
||||
found = true;
|
||||
connCache[r] = createNewSynAuth(r, function(err, sa){
|
||||
if ( err ) {
|
||||
delete connCache[r];
|
||||
return done(err);
|
||||
}
|
||||
return done(null, sa);
|
||||
});
|
||||
}
|
||||
}
|
||||
if ( !found ) {
|
||||
done(new Error('getConnectionFromURi: Cannot get rootURi for '+uri));
|
||||
}
|
||||
}
|
||||
|
||||
function removeCachedConnection(sa) {
|
||||
var found = false;
|
||||
// looking for cached connection
|
||||
for ( var root in connCache ) {
|
||||
if ( connCache[root] === sa) {
|
||||
delete connCache[root];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function http(o) {
|
||||
var that = {
|
||||
sa: null,
|
||||
err: null,
|
||||
errorCb: null,
|
||||
successCb: null
|
||||
};
|
||||
|
||||
if ( !o.headers ) {
|
||||
o.headers = {};
|
||||
}
|
||||
|
||||
var reconnect = 0;
|
||||
|
||||
var wp = emAuth.get('workplace');
|
||||
|
||||
if ( wp ) {
|
||||
synConn.opts.wpId = emAuth.get('workplace').ID;
|
||||
}
|
||||
|
||||
o.uri = supplant(o.uri, synConn.opts);
|
||||
|
||||
function connect() {
|
||||
getConnectionFromURi(o.uri, function(err, sa){
|
||||
if ( err ) { that.err = err; }
|
||||
that.sa = sa;
|
||||
|
||||
setTimeout(function() {
|
||||
if ( err ) { return that.errorCb(err); }
|
||||
runRequest();
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
function runRequest() {
|
||||
|
||||
var url;
|
||||
|
||||
if ( o.params ) {
|
||||
url = that.sa.getURL( buildUrl(o.uri, o.params) );
|
||||
delete o.params;
|
||||
} else {
|
||||
url = that.sa.getURL(o.uri);
|
||||
}
|
||||
|
||||
if ( o.data ) {
|
||||
if ( o.headers['Content-Type'] === 'application/x-www-form-urlencoded' ) {
|
||||
o.data = buildQueryString(o.data);
|
||||
}
|
||||
}
|
||||
|
||||
var opts = angular.extend({ url : url }, o);
|
||||
delete opts.uri;
|
||||
opts.method = opts.method || 'GET';
|
||||
|
||||
$http(opts).success(function(){
|
||||
that.successCb.apply(this, arguments);
|
||||
emsLoadingBar.stop();
|
||||
reconnect = 0;
|
||||
}).error(function(data){
|
||||
var err = new Error(data.ErrorText);
|
||||
err.ErrorCode = data.ErrorCode;
|
||||
|
||||
if ( err && err.ErrorCode == 403 ) {
|
||||
if ( reconnect == MAX_RECONNECT_COUNT ) {
|
||||
alert('Authentication failure. You will be logged out now. Shall this error repeat, contact your server administrator.');
|
||||
window.location.hash = '#/logout';
|
||||
return;
|
||||
}
|
||||
removeCachedConnection(that.sa);
|
||||
reconnect += 1;
|
||||
connect();
|
||||
} else {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift(err);
|
||||
that.errorCb.apply(this, args);
|
||||
emsLoadingBar.stop();
|
||||
}
|
||||
});
|
||||
emsLoadingBar.start();
|
||||
}
|
||||
|
||||
return {
|
||||
success: function(cb){
|
||||
that.successCb = cb;
|
||||
return this;
|
||||
},
|
||||
error: function(cb){
|
||||
that.errorCb = cb;
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function touch(uri, done) {
|
||||
getConnectionFromURi(uri, done);
|
||||
}
|
||||
|
||||
synConn.logout = function() {
|
||||
connCache = {};
|
||||
};
|
||||
|
||||
//getConnectionFromURi('wp/1/settings/GetTemplateMessage?ID=');
|
||||
|
||||
synConn.setOpts = setOpts;
|
||||
synConn.http = http;
|
||||
synConn.touch = touch;
|
||||
synConn.getBaseURL = getBaseURL;
|
||||
|
||||
return synConn;
|
||||
});
|
||||
});
|
@@ -0,0 +1,67 @@
|
||||
<div class="container" style="padding-top: 70px;">
|
||||
<div class="col-md-4 col-sm-4">
|
||||
<div class="form-group">
|
||||
<select class="form-control" ng-model="method">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
</select>
|
||||
<br>
|
||||
<label for="control-label">Query Params: <button class="btn btn-xs btn-default" ng-click="addParam()"><span class="glyphicon glyphicon-plus"></span></button> <button class="btn btn-xs btn-default" ng-click="toggleRawBody()">{{rawBodyBtn}}</button> </label>
|
||||
|
||||
<table class="table table-fixed" ng-show="!useRawBody">
|
||||
<tbody>
|
||||
<tr ng-repeat="p in params">
|
||||
<td><input type="text" class="form-control input-sm" ng-model="p.name" placeholder="Name"></td>
|
||||
<td><input type="text" class="form-control input-sm" ng-model="p.value" placeholder="Value"></td>
|
||||
<td style="width: 30px;"><button class="btn btn-default" ng-click="delParam($index)"><span class="glyphicon glyphicon-trash"></span></button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<textarea class="form-control" ng-show="useRawBody" ng-model="rawBody"></textarea>
|
||||
|
||||
<hr>
|
||||
<label for="control-label">Headers: <button class="btn btn-xs btn-default" ng-click="addHeader()"><span class="glyphicon glyphicon-plus"></span></button></label>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Content-Type: </label>
|
||||
<select ng-model="contentType">
|
||||
<option value=""></option>
|
||||
<option value="application/json">application/json</option>
|
||||
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table class="table table-fixed">
|
||||
<tbody>
|
||||
<tr ng-repeat="h in headers">
|
||||
<td><input type="text" class="form-control input-sm" ng-model="h.name" placeholder="Name"></td>
|
||||
<td><input type="text" class="form-control input-sm" ng-model="h.value" placeholder="Value"></td>
|
||||
<td style="width: 30px;"><button class="btn btn-default" ng-click="delHeader($index)"><span class="glyphicon glyphicon-trash"></span></button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-sm-9">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="URI" ng-model="uri">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<button class="btn btn-primary" ng-click="sendRequest()">Send Request</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<label class="control-label">Response</label>
|
||||
|
||||
<textarea ng-model="response" class="form-control" style="width: 100%; height: 300px;"></textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,21 @@
|
||||
<div ng-controller="ContactsCtrl" class="contacts-view">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 hide-xs-only" style="text-align: center;" >Please select group from the menu.</div>
|
||||
<!--- mobile menu -->
|
||||
<ul class="contacts-group-menu show-xs-only">
|
||||
<li>
|
||||
<h5>My Databases</h5>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.db"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h5>My Groups</h5>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.local"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<!--- /mobile menu -->
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,14 @@
|
||||
<ul class="nav">
|
||||
<li class="open">
|
||||
<a href="#">My Databases</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.db"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="open">
|
||||
<a href="#">My Groups</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.local"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
@@ -0,0 +1,32 @@
|
||||
<div ng-controller="ContactsCtrl" class="contacts-view">
|
||||
<div class="row split-view">
|
||||
<div class="col-sm-6" ng-if="contacts" id="contacts-list">
|
||||
<div infinite-scroll='contacts.nextPage()' infinite-scroll-disabled='contacts.busy' infinite-scroll-distance='20' infinite-scroll-box="contacts-list">
|
||||
<div class="contact" ng-repeat="c in contacts.items" ng-class="{'unsubscribed': !c.Subscribed}">
|
||||
<span class="name">{{c.First_Name}} {{c.Last_Name}} {{c.Recipient_Name}}</span>
|
||||
<span class="email"><{{c.Email}}></span>
|
||||
<span class="subscribed-date">{{c.Subscribe_Date | date:'MM/dd/yyyy @ h:mm a'}}</span>
|
||||
<span ng-show="!c.Subscribed" class="label label-default unsubscribed">unsubscrubed</span>
|
||||
<span ng-show="c.Fields" class="fields">
|
||||
<table>
|
||||
<tr ng-repeat="(key, val) in c.Fields">
|
||||
<td>{{key}}</td>
|
||||
<td>{{val}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 exclusion-list-pane" id="exclusion-list-container" ng-class="{'show': showExclusionList}">
|
||||
<div class="title">
|
||||
<h4>Exclusion list</h4>
|
||||
</div>
|
||||
<div id="exclusion-list">
|
||||
<ul ng-if="contactsExcList" class="list-unstyled" infinite-scroll='contactsExcList.nextPage()' infinite-scroll-disabled='contactsExcList.busy' infinite-scroll-distance='20' infinite-scroll-box="exclusion-list">
|
||||
<li ng-repeat="e in contactsExcList.items">{{e.Email}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,22 @@
|
||||
<ul class="nav gs-sidenav">
|
||||
<li ng-class="{'active': section == 'templates'}">
|
||||
<a href="#/workplaces/{{workplace.ID}}/templates">Templates</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': sub == catId}" ng-repeat="(catId, cat) in templateCats"><a href="#/workplaces/{{workplace.ID}}/templates/folder/{{catId}}"><span class="glyphicon glyphicon-folder-close"></span> {{cat}}</a></li>
|
||||
|
||||
<li ng-class="{'active': sub == 'trash'}" sub="trash"><a href="#/workplaces/{{workplace.ID}}/templates/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-class="{'active': section == 'drafts'}">
|
||||
<a href="#/workplaces/{{workplace.ID}}/drafts">Drafts</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': subSection == 'trash'}"><a href="#/workplaces/{{workplace.ID}}/drafts/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-class="{'active': section == 'outbox'}">
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Outbox</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': subSection == 'trash'}"><a href="#/workplaces/{{workplace.ID}}/outbox/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
@@ -0,0 +1,30 @@
|
||||
<div ng-controller="DraftsCtrl">
|
||||
<div class="row split-view">
|
||||
<div class="col-md-6 col-sm-6" id="messages-list-wrap">
|
||||
<ul class="messages-list" infinite-scroll='drafts.nextPage()' infinite-scroll-disabled='drafts.busy' infinite-scroll-distance='1' infinite-scroll-box="messages-list-wrap">
|
||||
<li ng-repeat="drf in drafts.items" ng-click="drfClick(drf)" ng-class="{'active': drf.ID == drafts.activeItem}">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="message-subject">{{drf.Subject}}</span></td>
|
||||
<td><span class="message-date">{{drf.ModTime | date:'MM/dd/yyyy @ h:mm a'}}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td ng-repeat="g in drf.GroupList">
|
||||
<span class="message-template">{{g.GroupName}}</span>
|
||||
</td>
|
||||
<td ng-repeat="a in drf.AccountList">
|
||||
<span class="message-group">{{a.AccountName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<span class="message-links" ng-if="subSection !== 'trash'"><a href="#/workplaces/{{workplace.ID}}/drafts/{{drf.ID}}/edit">Edit Message</a> | <a href="#" ng-click="drfDelete($event, drf)">Delete</a></span>
|
||||
|
||||
<span class="message-links" ng-if="subSection == 'trash'"><a ng-click="drfRestore($event, drf)">Restore</a> | <a class="text-danger" href="#" ng-click="drfDelete($event, drf)">Delete</a></span>
|
||||
</li>
|
||||
<li class="message-list-info" ng-show="drafts.busy">Loading data...</li>
|
||||
<li class="message-list-info" ng-show="!drafts.items.length && drafts.noMoreData">No drafts here yet</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6" ng-class="{ 'show': showPreview }" id="preview-container"></div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,71 @@
|
||||
<div class="container">
|
||||
<div class="row split-view editor">
|
||||
|
||||
<div class="col-md-9 col-sm-9">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="subject" class="col-xs-2 control-label">Subject:</label>
|
||||
<div class="col-xs-10">
|
||||
<input type="text" class="form-control" ng-model="item.Subject" id="subject" placeholder="Subject">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="editor-wrap">
|
||||
<textarea class="col" id="editor" data-ck-source="item" data-ck-editor></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<form class="form-horizontal" role="form">
|
||||
<button ng-click="sendNow()" class="btn btn-primary btn-md btn-margin">Send Now</button>
|
||||
<button class="btn btn-default btn-md btn-margin" ng-click="save()">Save</button>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="account" class="col-xs-1 control-label">From:</label>
|
||||
<div class="col-xs-11">
|
||||
<!-- <select name="from" id="from" class="form-control" ng-model="account">
|
||||
<option value="">Select Account</option>
|
||||
<option ng-selected="account == acc.ID" ng-repeat="acc in accounts" value="{{acc.ID}}">{{acc.AccountName}}</option>
|
||||
</select>
|
||||
-->
|
||||
<gt-select gt-model="account" options="acc.ID as acc.AccountName for acc in accounts"></gt-select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="to" class="col-xs-1 control-label">To:</label>
|
||||
<div class="col-xs-11">
|
||||
<gt-select multiple="true" gt-model="to" options="g.ID as g.GroupName for g in groups group by g.type" plural="groups"></gt-select>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<input type="checkbox" ng-model="enableTracking"> Track recipients' actions
|
||||
</label>
|
||||
<input ng-disabled="!enableTracking" type="text" class="form-control" placeholder="Campaign Name" ng-model="item.TrackName">
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input ng-disabled="!enableTracking" type="radio" ng-model="item.TrackMethod" name="analytics" id="analytics-google" value="1" checked>
|
||||
Google analytics
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input ng-disabled="!enableTracking" type="radio" ng-model="item.TrackMethod" name="analytics" id="analytics-piwik" value="2" checked>
|
||||
Piwik
|
||||
</label>
|
||||
</div>
|
||||
<hr>
|
||||
<label>Attachments <button ng-click="addCommonAtt()" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-plus"></span></button> </label>
|
||||
<ul class="list-unstyled em-file-list">
|
||||
<li ng-repeat="itm in item.AttachList"><i class="glyphicon glyphicon-file"></i><span>{{itm}}</span><span class="em-btn-remove"><i class="glyphicon glyphicon-trash" ng-click="removeAttachment(itm)"></i></span></li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,40 @@
|
||||
<div class="container">
|
||||
<div class="row split-view editor">
|
||||
|
||||
<div class="col-md-9 col-sm-9">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="subject" class="col-xs-2 control-label">Subject:</label>
|
||||
<div class="col-xs-10">
|
||||
<input type="text" class="form-control" ng-model="item.Subject" id="subject" placeholder="Subject">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="editor-wrap">
|
||||
<textarea class="col" id="editor" data-ck-source="item" data-ck-editor></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<form class="form-horizontal" role="form">
|
||||
<button class="btn btn-primary btn-md" ng-click="save()">Save</button>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="to" class="col-xs-1 control-label">To:</label>
|
||||
<div class="col-xs-11">
|
||||
<gt-select multiple="true" gt-model="to" options="g.ID as g.GroupName for g in groups group by g.type" plural="groups"></gt-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Attachments <button ng-click="addCommonAtt()" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-plus"></span></button> </label>
|
||||
<ul class="list-unstyled em-file-list">
|
||||
<li ng-repeat="itm in item.AttachList"><i class="glyphicon glyphicon-file"></i> {{itm}} <span class="em-btn-remove"><i class="glyphicon glyphicon-trash"></i></span></li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,83 @@
|
||||
<div class="container">
|
||||
<div class="row split-view editor">
|
||||
|
||||
<div class="col-md-9 col-sm-9">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="subject" class="col-xs-2 control-label">Subject:</label>
|
||||
<div class="col-xs-10">
|
||||
<input type="text" class="form-control" ng-model="item.Subject" id="subject" placeholder="Subject">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="editor-wrap">
|
||||
<textarea class="col" id="editor" data-ck-source="item" data-ck-editor></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<form class="form-horizontal" role="form">
|
||||
|
||||
<div ng-if="itemType=='template'">
|
||||
<button class="btn btn-primary btn-md" ng-click="save()">Save</button>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="to" class="col-xs-1 control-label">To:</label>
|
||||
<div class="col-xs-11">
|
||||
<gt-select multiple="true" gt-model="$parent.to" options="g.ID as g.GroupName for g in groups group by g.type" plural="groups"></gt-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div ng-if="itemType=='draft'">
|
||||
<button ng-click="sendNow()" class="btn btn-primary btn-md btn-margin">Send Now</button>
|
||||
<button class="btn btn-default btn-md btn-margin" ng-click="save()">Save</button>
|
||||
</div>
|
||||
|
||||
<div ng-if="itemType=='draft'">
|
||||
<div class="form-group">
|
||||
<label for="account" class="col-xs-1 control-label">From:</label>
|
||||
<div class="col-xs-11">
|
||||
<select name="from" id="from" class="form-control">
|
||||
<option value="">Select Account</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="to" class="col-xs-1 control-label">To:</label>
|
||||
<div class="col-xs-11">
|
||||
<select class="form-control" id="to">
|
||||
<option value="">Select Group</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<input type="checkbox"> Track recipients' actions
|
||||
</label>
|
||||
<input type="text" class="form-control" placeholder="Campaign Name">
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="analytics" id="analytics-google" value="google" checked>
|
||||
Google analytics
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="analytics" id="analytics-piwik" value="piwik" checked>
|
||||
Piwik
|
||||
</label>
|
||||
</div>
|
||||
<hr>
|
||||
<a href="#" ng-click="addAttachment()">Add Attachment</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,11 @@
|
||||
<div ng-class="{ 'show': $parent.showPreview }" id="preview-container" ng-show="!log"></div>
|
||||
<div id="sentlog" ng-show="log">
|
||||
<ul class="list-unstyled" infinite-scroll="log.nextPage()" infinite-scroll-disabled="log.busy" infinite-scroll-distance="1" infinite-scroll-box="sentlog">
|
||||
<li title="{{itm.StatusText}}" ng-repeat="itm in log.items" class="sentlog-item" ng-class="{ 'sentlog-item-success': itm.Status === 1, 'sentlog-item-temperror': itm.Status === 2, 'sentlog-item-error': itm.Status === 3}">
|
||||
<div class="sentlog-item-groupname">{{itm.GroupName}}<span class="sentlog-item-date">{{itm.SendDate | date:'MM/dd/yyyy @ h:mm a'}}</span></div>
|
||||
<div class="sentlog-item-recipient">{{itm.RecipientName}} <{{itm.Email}}></div>
|
||||
<div class="sentlog-item-response"><span>{{itm.LastResponse}}</span></div>
|
||||
</li>
|
||||
<li class="message-list-info" ng-show="log.busy">Loading data...</li>
|
||||
</ul>
|
||||
</div>
|
@@ -0,0 +1,46 @@
|
||||
<div class="dropdown gt-dropdown">
|
||||
<button class="btn btn-default gtms-dropdown-toggle form-control" type="button">
|
||||
<span>{{buttonTitle}}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="gt-dropdown dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
||||
<li ng-if="multiple" role="presentation">
|
||||
<a ng-click="aCheckAllClick($event)">
|
||||
<input type="checkbox" ng-model="$parent.allChecked" ng-click="iClick($event)"> {{ allChecked ? 'Uncheck All' : 'Check All' }}
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="multiple" role="presentation" class="divider"></li>
|
||||
|
||||
<!-- multiple mode -->
|
||||
|
||||
<li ng-if="multiple && groupedItems" role="presentation" class="dropdown-header" ng-repeat-start="(optGroup, opts) in groupedItems">{{optGroup}}</li>
|
||||
<li ng-if="multiple && groupedItems" role="presentation" ng-repeat-end ng-repeat="itm in opts">
|
||||
<a role="menuitem" tabindex="-1" ng-click="aClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="itm.checked" type="checkbox" name="{{inpName}}" ng-click="iClick($event)"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="multiple && items" role="presentation" ng-repeat="itm in items">
|
||||
<a role="menuitem" tabindex="-1" ng-click="aClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="itm.checked" type="checkbox" name="{{inpName}}" ng-click="iClick($event)"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<!-- single -->
|
||||
|
||||
<li ng-if="!multiple">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raNullClick($event)">
|
||||
<input value="" ng-model="$parent.singleResult" type="radio"> {{defaultButtonTitle}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="!multiple && groupedItems" role="presentation" class="dropdown-header" ng-repeat-start="(optGroup, opts) in groupedItems">{{optGroup}}</li>
|
||||
<li ng-if="!multiple && groupedItems" ng-repeat-end ng-repeat="itm in opts">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="$parent.$parent.singleResult" ng-click="raInpClick($event)" type="radio"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="!multiple && items" role="presentation" ng-repeat="itm in items">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="$parent.$parent.singleResult" ng-click="raInpClick($event)" type="radio"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
@@ -0,0 +1,46 @@
|
||||
<div class="dropdown gt-select">
|
||||
<button class="btn btn-default gtms-dropdown-toggle form-control" type="button">
|
||||
<span>{{buttonTitle}}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="gt-select dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
||||
<li ng-if="multiple" role="presentation">
|
||||
<a ng-click="aCheckAllClick($event)">
|
||||
<input type="checkbox" ng-model="$parent.allChecked" ng-click="iClick($event)"> {{ allChecked ? 'Uncheck All' : 'Check All' }}
|
||||
</a>
|
||||
</li>
|
||||
<li ng-if="multiple" role="presentation" class="divider"></li>
|
||||
|
||||
<!-- multiple mode -->
|
||||
|
||||
<li ng-if="multiple && groupedItems" role="presentation" class="dropdown-header" ng-repeat-start="(optGroup, opts) in groupedItems">{{optGroup}}</li>
|
||||
<li ng-if="multiple && groupedItems" role="presentation" ng-repeat-end ng-repeat="itm in opts">
|
||||
<a role="menuitem" tabindex="-1" ng-click="aClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="itm.checked" type="checkbox" name="{{inpName}}" ng-click="iClick($event)"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="multiple && items" role="presentation" ng-repeat="itm in items">
|
||||
<a role="menuitem" tabindex="-1" ng-click="aClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="itm.checked" type="checkbox" name="{{inpName}}" ng-click="iClick($event)"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<!-- single -->
|
||||
|
||||
<li ng-if="!multiple">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raNullClick($event)">
|
||||
<input value="" ng-model="$parent.singleResult" type="radio"> {{defaultButtonTitle}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="!multiple && groupedItems" role="presentation" class="dropdown-header" ng-repeat-start="(optGroup, opts) in groupedItems">{{optGroup}}</li>
|
||||
<li ng-if="!multiple && groupedItems" ng-repeat-end ng-repeat="itm in opts">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="$parent.$parent.singleResult" ng-click="raInpClick($event)" type="radio"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
<li ng-if="!multiple && items" role="presentation" ng-repeat="itm in items">
|
||||
<a role="menuitem" tabindex="-1" ng-click="raClick($event, itm)">
|
||||
<input value="{{itm.value}}" ng-model="$parent.$parent.singleResult" ng-click="raInpClick($event)" type="radio"> {{itm.label}}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
@@ -0,0 +1,34 @@
|
||||
<div class="container" ng-controller="LoginCtrl">
|
||||
|
||||
<form class="form-signin">
|
||||
<h2 class="form-signin-heading">G-Lock EasyMail 7</h2>
|
||||
|
||||
<div ng-show="selectServer">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="localhost" ng-model="conn.host">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" tabindex="-1">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li ng-repeat="h in savedHosts"><a ng-click="loadHost($index)"><strong>{{h.email}}</strong> @ {{h.host}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="hr">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control top-el" placeholder="Username" value="" autofocus ng-model="conn.email">
|
||||
<input type="password" class="form-control bottom-el" placeholder="Password" value="" ng-model="conn.password">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" ng-model="remember"> Remember me
|
||||
</label>
|
||||
<div ng-show="error" class="alert alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<strong>Error!</strong> {{error}}
|
||||
</div>
|
||||
<button class="btn btn-md btn-primary btn-block" type="button" ng-click="signIn()">Sign in</button>
|
||||
</form>
|
||||
|
||||
</div>
|
@@ -0,0 +1,30 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Select a File to Upload</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<table class="em-table-fixed em-table-upload">
|
||||
<tbody>
|
||||
<tr ng-repeat="f in files">
|
||||
<td>{{f.fileName}}</td>
|
||||
<td>
|
||||
<div class="progress progress-striped" ng-class="{'em-progress-bar-fallback': (!f.progressEnabled && f.status == 'uploading'), 'em-progress-bar-fallback-done': (!f.progressEnabled && f.status == 'done'), 'active': (f.progressEnabled && f.status == 'uploading') }">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: {{f.uploadedPct}}%">
|
||||
<span class="sr-only">{{f.uploadedPct}}% Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{f.status}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="file-upload-dropzone">
|
||||
Drop Files here or <div grf-upload-button="1" class="btn btn-default" id="choose-files">Click to Add</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" ng-click="close()">Done</button>
|
||||
</div>
|
@@ -0,0 +1,18 @@
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-click="toggleSelect()" ng-disabled="disabled" ng-class="{'error': !valid()}">
|
||||
{{header}} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<input class="form-control input-sm" type="text" ng-model="searchText.label" autofocus="autofocus" placeholder="Filter" />
|
||||
</li>
|
||||
<li ng-show="multiple" role="presentation" class="">
|
||||
<button class="btn btn-link btn-xs" ng-click="checkAll()" type="button"><i class="glyphicon glyphicon-ok"></i> Check all</button>
|
||||
<button class="btn btn-link btn-xs" ng-click="uncheckAll()" type="button"><i class="glyphicon glyphicon-remove"></i> Uncheck all</button>
|
||||
</li>
|
||||
<li ng-repeat="i in items | filter:searchText">
|
||||
<a ng-click="select(i); focus()">
|
||||
<i class='glyphicon' ng-class="{'glyphicon-ok': i.checked, 'empty': !i.checked}"></i> {{i.label}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
@@ -0,0 +1,29 @@
|
||||
<div ng-controller="EmailsCtrl">
|
||||
<div class="row split-view">
|
||||
<div class="col-sm-6" id="messages-list-wrap">
|
||||
<ul class="messages-list" infinite-scroll='emails.nextPage()' infinite-scroll-disabled='emails.busy' infinite-scroll-distance='1' infinite-scroll-box="messages-list-wrap">
|
||||
<li ng-repeat="eml in emails.items" ng-click="emlClick(eml)" eml-id="{{eml.ID}}" ng-class="{'active': eml.ID == emails.activeItem}">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="message-subject">{{eml.Subject}}</span></td>
|
||||
<td><span class="message-date">{{eml.CreateTime | date:'MM/dd/yyyy @ h:mm a'}}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span ng-repeat="g in eml.resGroupList" class="message-template">{{g.GroupName}}</span>
|
||||
</td>
|
||||
<td ng-repeat="a in eml.resAccountList">
|
||||
<span class="message-group">{{a.AccountName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<span class="message-links"><a href="#/workplaces/{{wpid}}/outbox/{{eml.ID}}/log" em-nobubble>{{eml.StatusMessage}}</a> | <a href="#" class="text-danger" ng-click="emDelete(eml)" em-nobubble>Delete</a></span>
|
||||
<span class="message-links pull-right"><a href="#/workplaces/{{wpid}}/outbox/{{eml.ID}}/processed" em-nobubble>Processed: {{eml.Processed}}</a> | <a href="#/workplaces/{{wpid}}/outbox/{{eml.ID}}/sent" em-nobubble>Sent: {{eml.Sent}}</a> | <a href="#/workplaces/{{wpid}}/outbox/{{eml.ID}}/failed" em-nobubble>Failed: {{eml.Failed}}</a></span>
|
||||
</li>
|
||||
<li class="message-list-info" ng-show="emails.busy">Loading data...</li>
|
||||
<li class="message-list-info" ng-show="!emails.items.length && emails.noMoreData">No emails here yet</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6" id="item-details" ui-view="itemdetails"></div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,8 @@
|
||||
<div class="container">
|
||||
<div id="contentsidebar" class="col-sm-2 gl-sidebar hide-xs-only" ui-view="contentsidebar">
|
||||
|
||||
</div>
|
||||
<div id="contentmain" class="col-xs-12 col-sm-10" ui-view="contentmain">
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,29 @@
|
||||
<div ng-controller="TemplatesCtrl">
|
||||
<div class="row split-view">
|
||||
<div class="col-md-6 col-sm-6" id="messages-list-wrap">
|
||||
<ul class="messages-list" infinite-scroll='templates.nextPage()' infinite-scroll-disabled='templates.busy' infinite-scroll-distance='1' infinite-scroll-box="messages-list-wrap">
|
||||
<li ng-repeat="tpl in templates.items" ng-click="tplClick(tpl)" ng-class="{'active': tpl.ID == templates.activeItem}">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="message-subject">{{tpl.Subject}}</span></td>
|
||||
<td><span class="message-date">{{tpl.ModTime | date:'MM/dd/yyyy @ h:mm a'}}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td ng-repeat="g in tpl.GroupList">
|
||||
<span class="message-template">{{g.GroupName}}</span>
|
||||
</td>
|
||||
<td ng-repeat="a in tpl.AccountList">
|
||||
<span class="message-group">{{a.AccountName}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<span class="message-links" ng-if="sub !== 'trash'"><a href="#/workplaces/{{workplace.ID}}/templates/{{tpl.ID}}/edit" em-nobubble>Edit Template</a> | <a ng-click="tplCreateMsg($event, tpl)">Create New Message</a> | <a class="text-danger" ng-click="tplDelete($event, tpl)">Delete</a></span>
|
||||
<span class="message-links" ng-if="sub == 'trash'"><a ng-click="tplRestore($event, tpl)">Restore</a> | <a class="text-danger" ng-click="tplDelete($event, tpl)">Delete</a></span>
|
||||
</li>
|
||||
<li class="message-list-info" ng-show="templates.busy">Loading data...</li>
|
||||
<li class="message-list-info" ng-show="!templates.items.length && templates.noMoreData">No templates here yet</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6" ng-class="{ 'show': showPreview }" id="preview-container"></div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,19 @@
|
||||
<div class="container" ng-controller="TopToolbarCtrl">
|
||||
<div class="top-toolbar row">
|
||||
<div class="col-md-2 col-sm-2 col-sm-2">
|
||||
<div class="btn-group btn-page-main">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
Console <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#/wp/1/console">Console</a></li>
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/outbox">Emails</a></li>
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/contacts">Contacts</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10 col-sm-10 col-sm-10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="toolbar-hr">
|
@@ -0,0 +1,40 @@
|
||||
<div class="container top-toolbar" ng-controller="TopToolbarCtrl">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="btn-group btn-page-main">
|
||||
<button type="button" class="btn btn-default gt-dropdown-toggle" data-toggle="dropdown">
|
||||
{{section | capitalize}} <span ng-if="subSection" class="show-xs-only-inline dvdr">{{subSection}}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu contacts-mobile-menu-top" role="menu">
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Emails</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/contacts">Contacts</a>
|
||||
<!--- mobile menu -->
|
||||
<ul class="nav show-xs-only">
|
||||
<li>
|
||||
<a href="#">My Databases</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.db"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">My Groups</a>
|
||||
<ul class="nav">
|
||||
<li ng-class="{'active': g.ID == gid}" ng-repeat="g in groups.local"><a href="#/workplaces/{{wpid}}/contacts/{{g.placement}}/{{g.ID}}">{{g.GroupName}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<!--- /mobile menu -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="toolbar-separator"></div>
|
||||
<!-- <button id="gl-btn-compose" type="button" class="hide-xs-only-inline btn btn-success btn-sm" ng-click="compose()">Compose</button> -->
|
||||
<!-- <button ng-class="{'active': showExclusionList}" class="hide-xs-only-inline btn btn-default" ng-click="toggleExclusionList()">Show Exclusion List</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="toolbar-hr">
|
@@ -0,0 +1,20 @@
|
||||
<div class="container" ng-controller="TopToolbarCtrl">
|
||||
<div class="top-toolbar">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-sm-9">
|
||||
<h4>
|
||||
<ol class="breadcrumb" ng-show="itemParams">
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/{{itemParams.root}}">{{itemParams.root | capitalize}}</a></li>
|
||||
<li ng-show="itemParams.folder"><a href="#/workplaces/{{workplace.ID}}/{{itemParams.root}}/{{itemParams.folder}}">{{itemParams.folder}}</a></li>
|
||||
<li class="active">{{itemParams.item.Subject}}</li>
|
||||
</ol>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{alert.msg}}</alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr class="toolbar-hr">
|
@@ -0,0 +1,46 @@
|
||||
<div class="container top-toolbar" ng-controller="TopToolbarCtrl">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="btn-group btn-page-main">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
{{uiSection}} <span class="show-xs-only-inline dvdr" > {{section | capitalize}} <span ng-if="subSection" class="dvdr">{{subSection}}</span></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Emails</a>
|
||||
<!--- mobile menu -->
|
||||
<ul class="nav show-xs-only" role="menu">
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/templates">Templates</a>
|
||||
<ul class="nav">
|
||||
<li ng-repeat="(catId, cat) in templateCats"><a href="#/workplaces/{{workplace.ID}}/templates/folder/{{catId}}"><span class="glyphicon glyphicon-folder-close"></span> {{cat}}</a></li>
|
||||
<li sub="trash"><a href="#/workplaces/{{workplace.ID}}/templates/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/drafts">Drafts</a>
|
||||
<ul class="nav">
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/drafts/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Outbox</a>
|
||||
<ul class="nav">
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/outbox/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<!--- /mobile menu -->
|
||||
</li>
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/contacts">Contacts</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="toolbar-separator"></div>
|
||||
<button id="gl-btn-compose" type="button" class="hide-xs-only-inline btn btn-success btn-sm" ng-click="compose()">Compose</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="toolbar-hr">
|
@@ -0,0 +1,49 @@
|
||||
<div class="container top-toolbar" ng-controller="TopToolbarCtrl">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="btn-group btn-page-main">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
{{uiSection}} <span class="show-xs-only-inline dvdr" > {{section | capitalize}} <span ng-if="subSection" class="dvdr">{{subSection}}</span></span> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Emails</a>
|
||||
<!--- mobile menu -->
|
||||
<ul class="nav show-xs-only" role="menu">
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/templates">Templates</a>
|
||||
<ul class="nav">
|
||||
<li ng-repeat="(catId, cat) in templateCats"><a href="#/workplaces/{{workplace.ID}}/templates/folder/{{catId}}"><span class="glyphicon glyphicon-folder-close"></span> {{cat}}</a></li>
|
||||
<li sub="trash"><a href="#/workplaces/{{workplace.ID}}/templates/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/drafts">Drafts</a>
|
||||
<ul class="nav">
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/drafts/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="#/workplaces/{{workplace.ID}}/outbox">Outbox</a>
|
||||
<ul class="nav">
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/outbox/trash"><span class="glyphicon glyphicon-trash"></span> Trash</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<!--- /mobile menu -->
|
||||
</li>
|
||||
<li><a href="#/workplaces/{{workplace.ID}}/contacts">Contacts</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="toolbar-separator"></div>
|
||||
<button id="gl-btn-compose" type="button" class="hide-xs-only-inline btn btn-success btn-sm" ng-click="compose()">Compose</button>
|
||||
<div class="hide-xxs-only-inline">
|
||||
<button ng-show="emlCanBeSent" type="button" ng-click="startSending()" class="btn btn-primary" ng-click="startSending">Start sending</button>
|
||||
<button ng-show="emlCanBeStopped" ng-click="stopSending()" type="button" class="btn btn-default">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="toolbar-hr">
|
@@ -0,0 +1,21 @@
|
||||
<div class="container">
|
||||
|
||||
<h3 style="margin-top: 80px;">Select workplace</h3>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Workplace name</th>
|
||||
<th>Description</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="wp in workplaces">
|
||||
<td><a href="#/workplaces/{{wp.ID}}/outbox">{{wp.WPName}}</a></td>
|
||||
<td>{{wp.Description}}</td>
|
||||
<td>{{wp.Created}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@@ -0,0 +1,7 @@
|
||||
<div id="sub-head" ui-view="topbar">
|
||||
</div>
|
||||
|
||||
<div id="main" class="content-view" ui-view="content">
|
||||
|
||||
|
||||
</div>
|
Reference in New Issue
Block a user