2015-07-20 00:09:47 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
/* global angular:false */
|
2016-01-13 16:00:37 +01:00
|
|
|
/* global showdown:false */
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
// deal with accessToken in the query, this is passed for example on password reset
|
|
|
|
|
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
|
|
|
|
if (search.accessToken) localStorage.token = search.accessToken;
|
|
|
|
|
|
2016-01-14 15:07:41 +01:00
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
// create main application module
|
2017-05-31 14:06:35 +02:00
|
|
|
var app = angular.module('Application', ['ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.bootstrap-slider', 'ngTld']);
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-01-14 15:07:41 +01:00
|
|
|
app.config(['NotificationProvider', function (NotificationProvider) {
|
|
|
|
|
NotificationProvider.setOptions({
|
2017-01-28 19:22:56 -08:00
|
|
|
delay: 5000,
|
2016-01-14 15:07:41 +01:00
|
|
|
startTop: 60,
|
2016-01-14 15:53:47 +01:00
|
|
|
positionX: 'left',
|
2017-01-10 15:55:02 +01:00
|
|
|
maxCount: 3,
|
2017-01-12 15:55:45 +01:00
|
|
|
templateUrl: 'notification.html'
|
2016-01-14 15:07:41 +01:00
|
|
|
});
|
|
|
|
|
}]);
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
// setup all major application routes
|
|
|
|
|
app.config(['$routeProvider', function ($routeProvider) {
|
|
|
|
|
$routeProvider.when('/', {
|
|
|
|
|
redirectTo: '/apps'
|
|
|
|
|
}).when('/users', {
|
|
|
|
|
controller: 'UsersController',
|
|
|
|
|
templateUrl: 'views/users.html'
|
|
|
|
|
}).when('/appstore', {
|
|
|
|
|
controller: 'AppStoreController',
|
|
|
|
|
templateUrl: 'views/appstore.html'
|
|
|
|
|
}).when('/appstore/:appId', {
|
|
|
|
|
controller: 'AppStoreController',
|
|
|
|
|
templateUrl: 'views/appstore.html'
|
|
|
|
|
}).when('/apps', {
|
|
|
|
|
controller: 'AppsController',
|
|
|
|
|
templateUrl: 'views/apps.html'
|
|
|
|
|
}).when('/account', {
|
|
|
|
|
controller: 'AccountController',
|
|
|
|
|
templateUrl: 'views/account.html'
|
|
|
|
|
}).when('/graphs', {
|
|
|
|
|
controller: 'GraphsController',
|
|
|
|
|
templateUrl: 'views/graphs.html'
|
2015-11-04 17:04:55 -08:00
|
|
|
}).when('/certs', {
|
|
|
|
|
controller: 'CertsController',
|
|
|
|
|
templateUrl: 'views/certs.html'
|
2017-06-02 10:24:46 +02:00
|
|
|
}).when('/email', {
|
|
|
|
|
controller: 'EmailController',
|
|
|
|
|
templateUrl: 'views/email.html'
|
2015-07-20 00:09:47 -07:00
|
|
|
}).when('/settings', {
|
|
|
|
|
controller: 'SettingsController',
|
|
|
|
|
templateUrl: 'views/settings.html'
|
2016-04-30 18:57:55 -07:00
|
|
|
}).when('/activity', {
|
|
|
|
|
controller: 'ActivityController',
|
|
|
|
|
templateUrl: 'views/activity.html'
|
2015-08-04 11:33:17 +02:00
|
|
|
}).when('/support', {
|
|
|
|
|
controller: 'SupportController',
|
|
|
|
|
templateUrl: 'views/support.html'
|
2016-06-07 22:53:36 +02:00
|
|
|
}).when('/tokens', {
|
|
|
|
|
controller: 'TokensController',
|
|
|
|
|
templateUrl: 'views/tokens.html'
|
2015-07-20 00:09:47 -07:00
|
|
|
}).otherwise({ redirectTo: '/'});
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
// keep in sync with appdb.js
|
|
|
|
|
var ISTATES = {
|
|
|
|
|
PENDING_INSTALL: 'pending_install',
|
2016-06-17 19:11:29 -05:00
|
|
|
PENDING_CLONE: 'pending_clone',
|
2015-07-20 00:09:47 -07:00
|
|
|
PENDING_CONFIGURE: 'pending_configure',
|
|
|
|
|
PENDING_UNINSTALL: 'pending_uninstall',
|
|
|
|
|
PENDING_RESTORE: 'pending_restore',
|
|
|
|
|
PENDING_UPDATE: 'pending_update',
|
2015-07-20 10:43:19 -07:00
|
|
|
PENDING_FORCE_UPDATE: 'pending_force_update',
|
2015-07-20 00:09:47 -07:00
|
|
|
PENDING_BACKUP: 'pending_backup',
|
|
|
|
|
ERROR: 'error',
|
|
|
|
|
INSTALLED: 'installed'
|
|
|
|
|
};
|
|
|
|
|
var HSTATES = {
|
|
|
|
|
HEALTHY: 'healthy',
|
|
|
|
|
UNHEALTHY: 'unhealthy',
|
|
|
|
|
ERROR: 'error',
|
|
|
|
|
DEAD: 'dead'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
app.filter('installError', function () {
|
|
|
|
|
return function (app) {
|
|
|
|
|
if (app.installationState === ISTATES.ERROR) return true;
|
|
|
|
|
if (app.installationState === ISTATES.INSTALLED) {
|
|
|
|
|
// app.health can also be null to indicate insufficient data
|
|
|
|
|
if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('installSuccess', function () {
|
|
|
|
|
return function (app) {
|
|
|
|
|
return app.installationState === ISTATES.INSTALLED;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-06-07 13:46:45 +02:00
|
|
|
app.filter('activeOAuthClients', function () {
|
|
|
|
|
return function (clients, user) {
|
|
|
|
|
return clients.filter(function (c) { return user.admin || (c.activeTokens && c.activeTokens.length > 0); });
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-11-30 17:31:37 +01:00
|
|
|
app.filter('prettyAppMessage', function () {
|
|
|
|
|
return function (message) {
|
2017-01-10 13:00:02 +01:00
|
|
|
if (message === 'ETRYAGAIN') return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and restore this app.';
|
2016-11-30 17:31:37 +01:00
|
|
|
return message;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-12-30 12:51:30 +01:00
|
|
|
app.filter('prettyMemory', function () {
|
|
|
|
|
return function (memory) {
|
|
|
|
|
// Adjust the default memory limit if it changes
|
|
|
|
|
return memory ? Math.floor(memory / 1024 / 1024) : 256;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
app.filter('installationActive', function () {
|
|
|
|
|
return function(app) {
|
|
|
|
|
if (app.installationState === ISTATES.ERROR) return false;
|
|
|
|
|
if (app.installationState === ISTATES.INSTALLED) return false;
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('installationStateLabel', function() {
|
2017-01-10 13:00:02 +01:00
|
|
|
// for better DNS errors
|
|
|
|
|
function detailedError(app) {
|
|
|
|
|
if (app.installationProgress === 'ETRYAGAIN') return 'DNS Error';
|
|
|
|
|
return 'Error';
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
return function(app) {
|
|
|
|
|
var waiting = app.progress === 0 ? ' (Waiting)' : '';
|
|
|
|
|
|
|
|
|
|
switch (app.installationState) {
|
2016-06-17 19:11:29 -05:00
|
|
|
case ISTATES.PENDING_INSTALL:
|
|
|
|
|
case ISTATES.PENDING_CLONE:
|
|
|
|
|
return 'Installing' + waiting;
|
2015-09-08 15:49:10 +02:00
|
|
|
case ISTATES.PENDING_CONFIGURE: return 'Configuring' + waiting;
|
|
|
|
|
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
|
|
|
|
|
case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
|
|
|
|
|
case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
|
|
|
|
|
case ISTATES.PENDING_FORCE_UPDATE: return 'Updating' + waiting;
|
|
|
|
|
case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
|
2017-01-10 13:00:02 +01:00
|
|
|
case ISTATES.ERROR: return detailedError(app);
|
2015-07-20 00:09:47 -07:00
|
|
|
case ISTATES.INSTALLED: {
|
|
|
|
|
if (app.runState === 'running') {
|
|
|
|
|
if (!app.health) return 'Starting...'; // no data yet
|
|
|
|
|
if (app.health === HSTATES.HEALTHY) return 'Running';
|
|
|
|
|
return 'Not responding'; // dead/exit/unhealthy
|
|
|
|
|
} else if (app.runState === 'pending_start') return 'Starting...';
|
|
|
|
|
else if (app.runState === 'pending_stop') return 'Stopping...';
|
|
|
|
|
else if (app.runState === 'stopped') return 'Stopped';
|
|
|
|
|
else return app.runState;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: return app.installationState;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('readyToUpdate', function () {
|
|
|
|
|
return function (apps) {
|
|
|
|
|
return apps.every(function (app) {
|
|
|
|
|
return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('inProgressApps', function () {
|
|
|
|
|
return function (apps) {
|
|
|
|
|
return apps.filter(function (app) {
|
|
|
|
|
return app.installationState !== ISTATES.ERROR && app.installationState !== ISTATES.INSTALLED;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-02-25 15:53:36 +01:00
|
|
|
app.filter('ignoreAdminGroup', function () {
|
|
|
|
|
return function (groups) {
|
|
|
|
|
return groups.filter(function (group) {
|
|
|
|
|
if (group.id) return group.id !== 'admin';
|
|
|
|
|
return group !== 'admin';
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
app.filter('applicationLink', function() {
|
|
|
|
|
return function(app) {
|
|
|
|
|
if (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY) {
|
|
|
|
|
return 'https://' + app.fqdn;
|
|
|
|
|
} else {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('prettyHref', function () {
|
|
|
|
|
return function (input) {
|
|
|
|
|
if (!input) return input;
|
|
|
|
|
if (input.indexOf('http://') === 0) return input.slice('http://'.length);
|
|
|
|
|
if (input.indexOf('https://') === 0) return input.slice('https://'.length);
|
|
|
|
|
return input;
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('prettyDate', function () {
|
|
|
|
|
// http://ejohn.org/files/pretty.js
|
|
|
|
|
return function prettyDate(time) {
|
|
|
|
|
var date = new Date(time),
|
|
|
|
|
diff = (((new Date()).getTime() - date.getTime()) / 1000) + 30, // add 30seconds for clock skew
|
|
|
|
|
day_diff = Math.floor(diff / 86400);
|
|
|
|
|
|
2016-07-09 13:06:59 -07:00
|
|
|
if (isNaN(day_diff) || day_diff < 0)
|
2017-03-02 14:34:14 -08:00
|
|
|
return 'just now';
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-01-13 16:00:37 +01:00
|
|
|
return day_diff === 0 && (
|
2015-07-20 00:09:47 -07:00
|
|
|
diff < 60 && 'just now' ||
|
|
|
|
|
diff < 120 && '1 minute ago' ||
|
|
|
|
|
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
|
|
|
|
|
diff < 7200 && '1 hour ago' ||
|
|
|
|
|
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
|
2016-01-13 16:00:37 +01:00
|
|
|
day_diff === 1 && 'Yesterday' ||
|
2015-07-20 00:09:47 -07:00
|
|
|
day_diff < 7 && day_diff + ' days ago' ||
|
2016-07-09 13:06:59 -07:00
|
|
|
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
|
|
|
|
|
day_diff < 365 && Math.round( day_diff / 30 ) + ' months ago' ||
|
|
|
|
|
Math.round( day_diff / 365 ) + ' years ago';
|
2015-07-20 00:09:47 -07:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.filter('markdown2html', function () {
|
2016-01-13 16:16:40 +01:00
|
|
|
var converter = new showdown.Converter({
|
|
|
|
|
extensions: ['targetblank'],
|
|
|
|
|
simplifiedAutoLink: true,
|
|
|
|
|
strikethrough: true,
|
|
|
|
|
tables: true
|
|
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
return function (text) {
|
|
|
|
|
return converter.makeHtml(text);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-11-24 15:33:45 +01:00
|
|
|
app.filter('postInstallMessage', function () {
|
|
|
|
|
var SSO_MARKER = '=== sso ===';
|
|
|
|
|
|
|
|
|
|
return function (text, app) {
|
2016-11-24 15:42:41 +01:00
|
|
|
if (!text) return '';
|
2016-11-24 15:33:45 +01:00
|
|
|
if (!app) return text;
|
|
|
|
|
|
|
|
|
|
var parts = text.split(SSO_MARKER);
|
2017-03-08 15:02:11 -08:00
|
|
|
if (parts.length === 1) {
|
|
|
|
|
// [^] matches even newlines. '?' makes it non-greedy
|
|
|
|
|
if (app.sso) return text.replace(/\<nosso\>[^]*?\<\/nosso\>/g, '');
|
|
|
|
|
else return text.replace(/\<sso\>[^]*?\<\/sso\>/g, '');
|
|
|
|
|
}
|
2016-11-24 15:33:45 +01:00
|
|
|
|
|
|
|
|
if (app.sso) return parts[1];
|
|
|
|
|
else return parts[0];
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2017-03-07 12:44:17 -08:00
|
|
|
// keep this in sync with eventlog.js and CLI tool
|
2016-04-30 19:49:50 -07:00
|
|
|
var ACTION_ACTIVATE = 'cloudron.activate';
|
|
|
|
|
var ACTION_APP_CONFIGURE = 'app.configure';
|
|
|
|
|
var ACTION_APP_INSTALL = 'app.install';
|
|
|
|
|
var ACTION_APP_RESTORE = 'app.restore';
|
|
|
|
|
var ACTION_APP_UNINSTALL = 'app.uninstall';
|
|
|
|
|
var ACTION_APP_UPDATE = 'app.update';
|
2016-05-01 11:42:12 -07:00
|
|
|
var ACTION_APP_UPDATE = 'app.update';
|
2017-03-02 17:14:56 +01:00
|
|
|
var ACTION_APP_LOGIN = 'app.login';
|
2016-05-01 11:42:12 -07:00
|
|
|
var ACTION_BACKUP_FINISH = 'backup.finish';
|
|
|
|
|
var ACTION_BACKUP_START = 'backup.start';
|
2016-04-30 22:27:33 -07:00
|
|
|
var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
|
2016-04-30 19:49:50 -07:00
|
|
|
var ACTION_CLI_MODE = 'settings.climode';
|
2016-05-02 09:39:38 -07:00
|
|
|
var ACTION_START = 'cloudron.start';
|
2016-04-30 19:49:50 -07:00
|
|
|
var ACTION_UPDATE = 'cloudron.update';
|
|
|
|
|
var ACTION_USER_ADD = 'user.add';
|
2016-04-30 23:16:37 -07:00
|
|
|
var ACTION_USER_LOGIN = 'user.login';
|
2016-04-30 19:49:50 -07:00
|
|
|
var ACTION_USER_REMOVE = 'user.remove';
|
|
|
|
|
var ACTION_USER_UPDATE = 'user.update';
|
|
|
|
|
|
|
|
|
|
app.filter('eventLogDetails', function() {
|
2017-03-07 12:44:17 -08:00
|
|
|
// NOTE: if you change this, the CLI tool (cloudron machine eventlog) probably needs fixing as well
|
2016-04-30 19:49:50 -07:00
|
|
|
return function(eventLog) {
|
2016-05-01 11:42:12 -07:00
|
|
|
var source = eventLog.source;
|
2016-04-30 19:49:50 -07:00
|
|
|
var data = eventLog.data;
|
2016-05-02 09:32:39 -07:00
|
|
|
var errorMessage = data.errorMessage;
|
2016-04-30 19:49:50 -07:00
|
|
|
|
|
|
|
|
switch (eventLog.action) {
|
2016-05-02 09:32:39 -07:00
|
|
|
case ACTION_ACTIVATE: return 'Cloudron activated';
|
|
|
|
|
case ACTION_APP_CONFIGURE: return 'App ' + data.appId + ' was configured';
|
2016-05-02 10:33:29 -07:00
|
|
|
case ACTION_APP_INSTALL: return 'App ' + data.manifest.id + '@' + data.manifest.version + ' installed at ' + data.location + ' with id ' + data.appId;
|
|
|
|
|
case ACTION_APP_RESTORE: return 'App ' + data.appId + ' restored';
|
|
|
|
|
case ACTION_APP_UNINSTALL: return 'App ' + data.appId + ' uninstalled';
|
|
|
|
|
case ACTION_APP_UPDATE: return 'App ' + data.appId + ' updated to version ' + data.toManifest.id + '@' + data.toManifest.version;
|
2017-03-02 17:14:56 +01:00
|
|
|
case ACTION_APP_LOGIN: return 'App ' + data.appId + ' logged in';
|
2016-05-02 09:32:39 -07:00
|
|
|
case ACTION_BACKUP_START: return 'Backup started';
|
2016-05-02 10:33:29 -07:00
|
|
|
case ACTION_BACKUP_FINISH: return 'Backup finished. ' + (errorMessage ? ('error: ' + errorMessage) : ('id: ' + data.filename));
|
2016-05-02 09:32:39 -07:00
|
|
|
case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : 'succeeded');
|
2016-04-30 19:49:50 -07:00
|
|
|
case ACTION_CLI_MODE: return 'CLI mode was ' + (data.enabled ? 'enabled' : 'disabled');
|
2016-05-02 09:39:38 -07:00
|
|
|
case ACTION_START: return 'Cloudron started with version ' + data.version;
|
2016-05-02 09:32:39 -07:00
|
|
|
case ACTION_UPDATE: return 'Updating to version ' + data.boxUpdateInfo.version;
|
2016-05-02 10:33:29 -07:00
|
|
|
case ACTION_USER_ADD: return 'User ' + data.email + ' added with id ' + data.userId;
|
|
|
|
|
case ACTION_USER_LOGIN: return 'User ' + data.userId + ' logged in';
|
2016-05-02 09:32:39 -07:00
|
|
|
case ACTION_USER_REMOVE: return 'User ' + data.userId + ' removed';
|
|
|
|
|
case ACTION_USER_UPDATE: return 'User ' + data.userId + ' updated';
|
2016-04-30 19:49:50 -07:00
|
|
|
default: return eventLog.action;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2015-07-20 00:09:47 -07:00
|
|
|
// custom directive for dynamic names in forms
|
|
|
|
|
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
|
|
|
|
|
app.directive('laterName', function () { // (2)
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'A',
|
|
|
|
|
require: ['?ngModel', '^?form'], // (3)
|
|
|
|
|
link: function postLink(scope, elem, attrs, ctrls) {
|
|
|
|
|
attrs.$set('name', attrs.laterName);
|
|
|
|
|
|
|
|
|
|
var modelCtrl = ctrls[0]; // (3)
|
|
|
|
|
var formCtrl = ctrls[1]; // (3)
|
|
|
|
|
if (modelCtrl && formCtrl) {
|
|
|
|
|
modelCtrl.$name = attrs.name; // (4)
|
|
|
|
|
formCtrl.$addControl(modelCtrl); // (2)
|
|
|
|
|
scope.$on('$destroy', function () {
|
|
|
|
|
formCtrl.$removeControl(modelCtrl); // (5)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
2016-01-25 16:21:20 +01:00
|
|
|
|
|
|
|
|
app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
|
|
|
|
|
var original = $location.path;
|
|
|
|
|
$location.path = function (path, reload) {
|
|
|
|
|
if (reload === false) {
|
|
|
|
|
var lastRoute = $route.current;
|
|
|
|
|
var un = $rootScope.$on('$locationChangeSuccess', function () {
|
|
|
|
|
$route.current = lastRoute;
|
|
|
|
|
un();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return original.apply($location, [path]);
|
|
|
|
|
};
|
2016-04-04 18:35:38 +02:00
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
app.directive('ngClickSelect', function () {
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'AC',
|
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
|
element.bind('click', function () {
|
|
|
|
|
var selection = window.getSelection();
|
|
|
|
|
var range = document.createRange();
|
|
|
|
|
range.selectNodeContents(this);
|
|
|
|
|
selection.removeAllRanges();
|
|
|
|
|
selection.addRange(range);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
2016-06-09 10:16:13 -07:00
|
|
|
|
2016-09-22 14:52:29 +02:00
|
|
|
app.directive('ngClickReveal', function () {
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'A',
|
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
|
element.addClass('hand');
|
|
|
|
|
|
2016-09-22 15:26:04 +02:00
|
|
|
var value = '';
|
|
|
|
|
|
|
|
|
|
scope.$watch(attrs.ngClickReveal, function (newValue, oldValue) {
|
|
|
|
|
if (newValue !== oldValue) {
|
|
|
|
|
element.html('<i>hidden</i>');
|
|
|
|
|
value = newValue;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2016-09-22 14:52:29 +02:00
|
|
|
element.bind('click', function () {
|
2016-09-22 15:26:04 +02:00
|
|
|
element.text(value);
|
2016-09-22 14:52:29 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2016-06-09 10:16:13 -07:00
|
|
|
// https://codepen.io/webmatze/pen/isuHh
|
|
|
|
|
app.directive('tagInput', function () {
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'E',
|
|
|
|
|
scope: {
|
2016-06-09 10:33:10 -07:00
|
|
|
inputTags: '=taglist'
|
2016-06-09 10:16:13 -07:00
|
|
|
},
|
|
|
|
|
link: function ($scope, element, attrs) {
|
|
|
|
|
$scope.defaultWidth = 200;
|
2016-06-10 11:46:25 -07:00
|
|
|
$scope.tagText = ''; // current tag being edited
|
2016-06-09 10:16:13 -07:00
|
|
|
$scope.placeholder = attrs.placeholder;
|
|
|
|
|
$scope.tagArray = function () {
|
|
|
|
|
if ($scope.inputTags === undefined) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
return $scope.inputTags.split(',').filter(function (tag) {
|
|
|
|
|
return tag !== '';
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
$scope.addTag = function () {
|
|
|
|
|
var tagArray;
|
|
|
|
|
if ($scope.tagText.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
tagArray = $scope.tagArray();
|
|
|
|
|
tagArray.push($scope.tagText);
|
|
|
|
|
$scope.inputTags = tagArray.join(',');
|
|
|
|
|
return $scope.tagText = '';
|
|
|
|
|
};
|
|
|
|
|
$scope.deleteTag = function (key) {
|
|
|
|
|
var tagArray;
|
|
|
|
|
tagArray = $scope.tagArray();
|
|
|
|
|
if (tagArray.length > 0 && $scope.tagText.length === 0 && key === undefined) {
|
|
|
|
|
tagArray.pop();
|
|
|
|
|
} else {
|
|
|
|
|
if (key !== undefined) {
|
|
|
|
|
tagArray.splice(key, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $scope.inputTags = tagArray.join(',');
|
|
|
|
|
};
|
|
|
|
|
$scope.$watch('tagText', function (newVal, oldVal) {
|
|
|
|
|
var tempEl;
|
|
|
|
|
if (!(newVal === oldVal && newVal === undefined)) {
|
|
|
|
|
tempEl = $('<span>' + newVal + '</span>').appendTo('body');
|
|
|
|
|
$scope.inputWidth = tempEl.width() + 5;
|
|
|
|
|
if ($scope.inputWidth < $scope.defaultWidth) {
|
|
|
|
|
$scope.inputWidth = $scope.defaultWidth;
|
|
|
|
|
}
|
|
|
|
|
return tempEl.remove();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
element.bind('keydown', function (e) {
|
2016-06-09 16:11:57 -07:00
|
|
|
var key = e.which;
|
2016-06-09 10:16:13 -07:00
|
|
|
if (key === 9 || key === 13) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
if (key === 8) {
|
|
|
|
|
return $scope.$apply('deleteTag()');
|
|
|
|
|
}
|
|
|
|
|
});
|
2016-06-10 12:35:57 -07:00
|
|
|
element.bind('keyup', function (e) {
|
2016-06-09 16:11:57 -07:00
|
|
|
var key = e.which;
|
2016-09-28 13:06:02 -07:00
|
|
|
if (key === 9 || key === 13 || key === 32 || key === 188) {
|
2016-06-09 10:16:13 -07:00
|
|
|
e.preventDefault();
|
|
|
|
|
return $scope.$apply('addTag()');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
2016-06-09 16:11:57 -07:00
|
|
|
template:
|
2016-06-10 11:46:25 -07:00
|
|
|
'<div class="tag-input-container">' +
|
2016-06-09 16:11:57 -07:00
|
|
|
'<div class="input-tag" data-ng-repeat="tag in tagArray()">' +
|
|
|
|
|
'{{tag}}' +
|
|
|
|
|
'<div class="delete-tag" data-ng-click="deleteTag($index)">×</div>' +
|
|
|
|
|
'</div>' +
|
2016-06-10 12:35:57 -07:00
|
|
|
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
|
2016-06-09 16:11:57 -07:00
|
|
|
'</div>'
|
2016-06-09 10:16:13 -07:00
|
|
|
};
|
|
|
|
|
});
|
2017-05-31 14:06:35 +02:00
|
|
|
|
|
|
|
|
app.config(['fitTextConfigProvider', function (fitTextConfigProvider) {
|
|
|
|
|
fitTextConfigProvider.config = {
|
2017-06-01 15:16:38 +02:00
|
|
|
loadDelay: 250,
|
2017-06-01 15:30:13 +02:00
|
|
|
compressor: 0.9,
|
2017-05-31 14:06:35 +02:00
|
|
|
min: 8,
|
|
|
|
|
max: 24
|
|
|
|
|
};
|
|
|
|
|
}]);
|