2018-01-22 13:01:38 -08:00
'use strict' ;
/* global angular:false */
2018-05-25 13:56:15 +02:00
/* global moment:false */
2019-01-22 10:54:03 +01:00
/* global $:false */
2019-09-27 18:11:48 +02:00
/* global ERROR,ISTATES,HSTATES,RSTATES */
2018-01-22 13:01:38 -08:00
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
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 ;
// strip the accessToken and expiresAt, then preserve the rest
delete search . accessToken ;
delete search . expiresAt ;
// this will reload the page as this is not a hash change
window . location . search = encodeURIComponent ( Object . keys ( search ) . map ( function ( key ) { return key + '=' + search [ key ] ; } ) . join ( '&' ) ) ;
}
// create main application module
2020-10-19 14:37:19 +02:00
var app = angular . module ( 'Application' , [ 'pascalprecht.translate' , 'ngFitText' , 'ngRoute' , 'ngAnimate' , 'ngSanitize' , 'angular-md5' , 'base64' , 'slick' , 'ui-notification' , 'ui.bootstrap' , 'ui.bootstrap-slider' , 'ngTld' , 'ui.multiselect' ] ) ;
2018-01-22 13:01:38 -08:00
app . config ( [ 'NotificationProvider' , function ( NotificationProvider ) {
NotificationProvider . setOptions ( {
delay : 5000 ,
startTop : 60 ,
positionX : 'left' ,
templateUrl : 'notification.html'
} ) ;
} ] ) ;
2020-10-19 14:37:19 +02:00
app . config ( [ '$translateProvider' , function ( $translateProvider ) {
$translateProvider . useStaticFilesLoader ( {
prefix : 'translation/' ,
suffix : '.json'
} ) ;
$translateProvider . preferredLanguage ( 'en' ) ;
// $translateProvider.preferredLanguage('de');
} ] ) ;
2019-07-31 08:08:46 +02:00
// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
app . config ( function ( $sceDelegateProvider ) {
$sceDelegateProvider . resourceUrlWhitelist ( [
// Allow same origin resource loads.
'self' ,
2019-08-06 10:24:35 +02:00
// Allow loading from our assets domain.
'https://cloudron.io/**' ,
'https://staging.cloudron.io/**' ,
'https://dev.cloudron.io/**' ,
// Allow local development against the appstore pages
2019-08-04 11:32:42 +02:00
'http://localhost:5000/**'
2019-07-31 08:08:46 +02:00
] ) ;
} ) ;
2018-01-22 13:01:38 -08:00
// setup all major application routes
app . config ( [ '$routeProvider' , function ( $routeProvider ) {
$routeProvider . when ( '/' , {
redirectTo : '/apps'
} ) . when ( '/users' , {
controller : 'UsersController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/users.html?<%= revision %>'
2019-09-18 17:45:13 +02:00
} ) . when ( '/app/:appId/:view?' , {
2019-09-10 19:21:30 +02:00
controller : 'AppController' ,
templateUrl : 'views/app.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/appstore' , {
controller : 'AppStoreController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/appstore.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/appstore/:appId' , {
controller : 'AppStoreController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/appstore.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/apps' , {
controller : 'AppsController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/apps.html?<%= revision %>'
2019-11-07 11:07:57 +01:00
} ) . when ( '/profile' , {
controller : 'ProfileController' ,
templateUrl : 'views/profile.html?<%= revision %>'
2018-06-07 14:22:48 +02:00
} ) . when ( '/backups' , {
controller : 'BackupsController' ,
templateUrl : 'views/backups.html?<%= revision %>'
2020-03-18 17:53:50 -07:00
} ) . when ( '/branding' , {
controller : 'BrandingController' ,
templateUrl : 'views/branding.html?<%= revision %>'
2019-11-07 15:26:18 +01:00
} ) . when ( '/network' , {
controller : 'NetworkController' ,
templateUrl : 'views/network.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/domains' , {
controller : 'DomainsController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/domains.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/email' , {
2020-02-11 21:06:34 +01:00
controller : 'EmailsController' ,
templateUrl : 'views/emails.html?<%= revision %>'
2018-04-09 18:00:08 +02:00
} ) . when ( '/email/:domain' , {
controller : 'EmailController' ,
templateUrl : 'views/email.html?<%= revision %>'
2019-01-09 15:18:10 +01:00
} ) . when ( '/notifications' , {
controller : 'NotificationsController' ,
templateUrl : 'views/notifications.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/settings' , {
controller : 'SettingsController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/settings.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/activity' , {
controller : 'ActivityController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/activity.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . when ( '/support' , {
controller : 'SupportController' ,
2018-04-06 00:11:08 +02:00
templateUrl : 'views/support.html?<%= revision %>'
2018-11-26 08:59:04 +01:00
} ) . when ( '/system' , {
controller : 'SystemController' ,
templateUrl : 'views/system.html?<%= revision %>'
2020-09-28 15:16:02 +02:00
} ) . when ( '/services' , {
controller : 'ServicesController' ,
templateUrl : 'views/services.html?<%= revision %>'
2020-10-28 16:14:32 -07:00
} ) . when ( '/volumes' , {
controller : 'VolumesController' ,
templateUrl : 'views/volumes.html?<%= revision %>'
2018-01-22 13:01:38 -08:00
} ) . otherwise ( { redirectTo : '/' } ) ;
} ] ) ;
app . filter ( 'installError' , function ( ) {
return function ( app ) {
2019-09-19 19:46:20 +02:00
if ( ! app ) return false ;
2018-01-22 13:01:38 -08:00
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 ;
} ;
} ) ;
2019-08-29 14:28:45 -07:00
app . filter ( 'activeTask' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2019-08-29 14:28:45 -07:00
return app . taskId !== null ;
} ;
} ) ;
2018-01-22 13:01:38 -08:00
app . filter ( 'installSuccess' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2018-01-22 13:01:38 -08:00
return app . installationState === ISTATES . INSTALLED ;
} ;
} ) ;
2018-06-15 13:35:46 +02:00
app . filter ( 'appIsInstalledAndHealthy' , function ( ) {
return function ( app ) {
2019-09-18 18:06:03 +02:00
if ( ! app ) return false ;
2019-09-19 18:27:02 +02:00
return ( app . installationState === ISTATES . INSTALLED && app . health === HSTATES . HEALTHY && app . runState === RSTATES . RUNNING ) ;
2019-09-10 13:36:29 -07:00
} ;
2018-06-15 13:35:46 +02:00
} ) ;
2019-09-19 19:46:20 +02:00
app . filter ( 'applicationLink' , function ( ) {
return function ( app ) {
if ( ! app ) return '' ;
if ( app . installationState === ISTATES . INSTALLED && app . health === HSTATES . HEALTHY && app . runState === RSTATES . RUNNING && ! app . pendingPostInstallConfirmation ) {
return 'https://' + app . fqdn ;
} else {
return '' ;
}
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears when an item in app grid is clicked
2019-09-07 09:24:39 +02:00
app . filter ( 'prettyAppErrorMessage' , function ( ) {
2019-07-31 11:40:16 -07:00
return function ( app ) {
2019-09-07 09:24:39 +02:00
if ( ! app ) return '' ;
2019-07-31 11:43:14 -07:00
if ( app . installationState === ISTATES . INSTALLED ) {
// app.health can also be null to indicate insufficient data
if ( app . health === HSTATES . UNHEALTHY ) return 'The app is not responding to health checks. Check the logs for any error messages.' ;
}
2019-09-05 18:15:05 -07:00
if ( app . error . reason === 'Access Denied' ) {
if ( app . error . domain ) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.' ;
} else if ( app . error . reason === 'Already Exists' ) {
if ( app . error . domain ) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.' ;
}
return app . error . message ;
2018-01-22 13:01:38 -08:00
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears as tool tip in app grid
2019-09-05 10:05:01 -07:00
app . filter ( 'appProgressMessage' , function ( ) {
2019-07-31 11:40:16 -07:00
return function ( app ) {
2019-09-05 10:05:01 -07:00
var message = app . message || ( app . error ? app . error . message : '' ) ;
2018-01-22 13:01:38 -08:00
return message ;
} ;
} ) ;
2020-09-26 17:50:23 +02:00
// see apps.js $scope.states
app . filter ( 'selectedStateFilter' , function ( ) {
return function selectedStateFilter ( apps , selectedState ) {
return apps . filter ( function ( app ) {
if ( ! selectedState || ! selectedState . state ) return true ;
if ( selectedState . state === 'running' ) return app . runState === 'running' && app . health === 'healthy' && app . installationState === 'installed' ;
2020-10-06 13:01:01 -07:00
if ( selectedState . state === 'stopped' ) return app . runState === 'stopped' ;
2020-09-26 17:50:23 +02:00
2020-10-06 13:01:01 -07:00
return app . runState === 'running' && ( app . health !== 'healthy' || app . installationState !== 'installed' ) ; // not responding
2020-09-26 17:50:23 +02:00
} ) ;
} ;
} ) ;
2019-04-15 14:31:12 +02:00
app . filter ( 'selectedTagFilter' , function ( ) {
return function selectedTagFilter ( apps , selectedTags ) {
return apps . filter ( function ( app ) {
if ( selectedTags . length === 0 ) return true ;
2019-05-17 13:01:23 -07:00
if ( ! app . tags ) return false ;
2019-04-15 14:31:12 +02:00
2019-05-17 13:01:23 -07:00
for ( var i = 0 ; i < selectedTags . length ; i ++ ) {
if ( app . tags . indexOf ( selectedTags [ i ] ) === - 1 ) return false ;
}
return true ;
2019-04-15 14:31:12 +02:00
} ) ;
} ;
} ) ;
app . filter ( 'selectedDomainFilter' , function ( ) {
2019-05-20 23:40:02 +02:00
return function selectedDomainFilter ( apps , selectedDomain ) {
2019-04-15 14:31:12 +02:00
return apps . filter ( function ( app ) {
2019-05-20 23:40:02 +02:00
if ( ! selectedDomain ) return true ;
if ( selectedDomain . _alldomains ) return true ; // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN
2019-04-15 14:31:12 +02:00
2019-05-20 23:40:02 +02:00
if ( selectedDomain . domain === app . domain ) return true ;
return ! ! app . alternateDomains . find ( function ( ad ) { return ad . domain === selectedDomain . domain ; } ) ;
2019-04-15 14:31:12 +02:00
} ) ;
} ;
} ) ;
2020-01-06 15:27:31 +01:00
app . filter ( 'appSearchFilter' , function ( ) {
return function appSearchFilter ( apps , appSearch ) {
return apps . filter ( function ( app ) {
if ( ! appSearch ) return true ;
2020-08-14 09:32:07 -07:00
appSearch = appSearch . toLowerCase ( ) ;
return app . fqdn . indexOf ( appSearch ) !== - 1
|| ( app . label && app . label . toLowerCase ( ) . indexOf ( appSearch ) !== - 1 )
|| ( app . manifest . title && app . manifest . title . toLowerCase ( ) . indexOf ( appSearch ) !== - 1 ) ;
2020-01-06 15:27:31 +01:00
} ) ;
} ;
} ) ;
2019-04-15 14:31:12 +02:00
app . filter ( 'prettyDomains' , function ( ) {
return function prettyDomains ( domains ) {
return domains . map ( function ( d ) { return d . domain ; } ) . join ( ', ' ) ;
} ;
} ) ;
2018-01-22 13:01:38 -08:00
app . filter ( 'installationActive' , function ( ) {
2019-08-30 21:21:44 +02:00
return function ( app ) {
2018-01-22 13:01:38 -08:00
if ( app . installationState === ISTATES . ERROR ) return false ;
if ( app . installationState === ISTATES . INSTALLED ) return false ;
return true ;
} ;
} ) ;
2019-07-31 11:38:28 -07:00
// this appears in the app grid
2019-08-30 21:21:44 +02:00
app . filter ( 'installationStateLabel' , function ( ) {
2020-05-12 21:30:57 -07:00
return function ( app ) {
2019-09-11 21:24:02 +02:00
if ( ! app ) return '' ;
2019-09-23 22:45:14 +02:00
var waiting = app . progress === 0 ? ' (Queued)' : '' ;
2018-01-22 13:01:38 -08:00
switch ( app . installationState ) {
case ISTATES . PENDING _INSTALL :
return 'Installing' + waiting ;
2019-09-19 09:28:24 -07:00
case ISTATES . PENDING _CLONE :
return 'Cloning' + waiting ;
2019-09-10 14:30:44 -07:00
case ISTATES . PENDING _LOCATION _CHANGE :
case ISTATES . PENDING _CONFIGURE :
case ISTATES . PENDING _RECREATE _CONTAINER :
case ISTATES . PENDING _DEBUG :
return 'Configuring' + waiting ;
2019-09-19 09:28:24 -07:00
case ISTATES . PENDING _RESIZE :
return 'Resizing' + waiting ;
2019-09-10 14:30:44 -07:00
case ISTATES . PENDING _DATA _DIR _MIGRATION :
return 'Migrating data' + waiting ;
2018-01-22 13:01:38 -08:00
case ISTATES . PENDING _UNINSTALL : return 'Uninstalling' + waiting ;
case ISTATES . PENDING _RESTORE : return 'Restoring' + waiting ;
case ISTATES . PENDING _UPDATE : return 'Updating' + waiting ;
case ISTATES . PENDING _BACKUP : return 'Backing up' + waiting ;
2019-09-22 01:01:47 -07:00
case ISTATES . PENDING _START : return 'Starting' + waiting ;
case ISTATES . PENDING _STOP : return 'Stopping' + waiting ;
2019-12-20 11:18:48 -08:00
case ISTATES . PENDING _RESTART : return 'Restarting' + waiting ;
2019-08-30 21:21:44 +02:00
case ISTATES . ERROR : {
2019-09-18 15:54:19 +02:00
if ( app . error && app . error . message === 'ETRYAGAIN' ) return 'DNS Error' ;
return 'Error' ;
2019-08-30 21:21:44 +02:00
}
2018-01-22 13:01:38 -08:00
case ISTATES . INSTALLED : {
2018-05-15 12:07:57 -07:00
if ( app . debugMode ) {
2019-12-20 11:53:06 -08:00
return 'Recovery Mode' ;
2018-05-15 12:07:57 -07:00
} else if ( app . runState === 'running' ) {
2018-01-22 13:01:38 -08:00
if ( ! app . health ) return 'Starting...' ; // no data yet
if ( app . health === HSTATES . HEALTHY ) return 'Running' ;
return 'Not responding' ; // dead/exit/unhealthy
2019-09-22 01:01:47 -07:00
} else if ( app . runState === 'stopped' ) return 'Stopped' ;
2018-01-22 13:01:38 -08:00
else return app . runState ;
}
default : return app . installationState ;
}
} ;
} ) ;
2019-09-23 23:28:14 -07:00
app . filter ( 'taskName' , function ( ) {
2019-11-23 17:49:13 -08:00
return function ( installationState ) {
switch ( installationState ) {
2019-09-23 23:28:14 -07:00
case ISTATES . PENDING _INSTALL : return 'install' ;
case ISTATES . PENDING _CLONE : return 'clone' ;
case ISTATES . PENDING _LOCATION _CHANGE : return 'location change' ;
case ISTATES . PENDING _CONFIGURE : return 'configure' ;
case ISTATES . PENDING _RECREATE _CONTAINER : return 'create container' ;
case ISTATES . PENDING _DEBUG : return 'debug' ;
case ISTATES . PENDING _RESIZE : return 'resize' ;
case ISTATES . PENDING _DATA _DIR _MIGRATION : return 'data migration' ;
case ISTATES . PENDING _UNINSTALL : return 'uninstall' ;
case ISTATES . PENDING _RESTORE : return 'restore' ;
case ISTATES . PENDING _UPDATE : return 'update' ;
case ISTATES . PENDING _BACKUP : return 'backup' ;
case ISTATES . PENDING _START : return 'start app' ;
case ISTATES . PENDING _STOP : return 'stop app' ;
2019-12-20 11:18:48 -08:00
case ISTATES . PENDING _RESTART : return 'restart app' ;
2019-11-23 17:49:13 -08:00
default : return installationState || '' ;
2019-09-23 23:28:14 -07:00
}
} ;
} ) ;
2019-09-23 23:51:15 -07:00
app . filter ( 'errorSuggestion' , function ( ) {
return function ( error ) {
2019-09-24 18:46:07 +02:00
if ( ! error ) return '' ;
2019-09-23 23:51:15 -07:00
switch ( error . reason ) {
2019-09-24 00:59:12 -07:00
case ERROR . ACCESS _DENIED :
if ( error . domain ) return 'Check the DNS credentials of ' + error . domain . domain + ' in the Domains & Certs view' ;
return '' ;
case ERROR . COLLECTD _ERROR : return 'Check if collectd is running on the server' ;
case ERROR . DATABASE _ERROR : return 'Check if MySQL database is running on the server' ;
case ERROR . DOCKER _ERROR : return 'Check if docker is running on the server' ;
case ERROR . DNS _ERROR : return 'Check if the DNS service of the domain is running' ;
case ERROR . LOGROTATE _ERROR : return 'Check if logrotate is running on the server' ;
case ERROR . NETWORK _ERROR : return 'Check if there are any network issues on the server' ;
case ERROR . REVERSEPROXY _ERROR : return 'Check if nginx is running on the server' ;
2019-09-23 23:51:15 -07:00
default : return '' ;
}
} ;
} ) ;
2018-01-22 13:01:38 -08:00
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 ;
} ) ;
} ;
} ) ;
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
2020-02-10 13:44:29 -08:00
return function prettyDate ( utc ) {
var date = new Date ( utc ) , // this converts utc into browser timezone and not cloudron timezone!
2018-01-22 13:01:38 -08:00
diff = ( ( ( new Date ( ) ) . getTime ( ) - date . getTime ( ) ) / 1000 ) + 30 , // add 30seconds for clock skew
day _diff = Math . floor ( diff / 86400 ) ;
if ( isNaN ( day _diff ) || day _diff < 0 )
return 'just now' ;
return day _diff === 0 && (
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' ) ||
day _diff === 1 && 'Yesterday' ||
day _diff < 7 && day _diff + ' days ago' ||
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' ;
} ;
} ) ;
2018-03-02 18:58:49 +01:00
app . filter ( 'prettyLongDate' , function ( ) {
2020-02-10 13:44:29 -08:00
return function prettyLongDate ( utc ) {
return moment ( utc ) . format ( 'MMMM Do YYYY, h:mm:ss a' ) ; // this converts utc into browser timezone and not cloudron timezone!
2018-03-02 18:58:49 +01:00
} ;
} ) ;
2019-03-06 15:54:21 +01:00
app . filter ( 'prettyShortDate' , function ( ) {
2020-02-10 13:44:29 -08:00
return function prettyShortDate ( utc ) {
return moment ( utc ) . format ( 'MMMM Do YYYY' ) ; // this converts utc into browser timezone and not cloudron timezone!
2019-03-06 15:54:21 +01:00
} ;
} ) ;
2020-02-12 15:37:05 +01:00
app . filter ( 'prettyEmailAddresses' , function ( ) {
return function prettyEmailAddresses ( addresses ) {
2020-02-12 23:17:24 -08:00
if ( ! addresses || addresses === '<>' ) return '<>' ;
2020-02-12 15:37:05 +01:00
if ( Array . isArray ( addresses ) ) return addresses . map ( function ( a ) { return a . slice ( 1 , - 1 ) ; } ) . join ( ', ' ) ;
return addresses . slice ( 1 , - 1 ) ;
} ;
} ) ;
2018-01-22 13:01:38 -08: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)
} ) ;
}
}
} ;
} ) ;
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 ] ) ;
} ;
} ] ) ;
app . directive ( 'ngClickSelect' , function ( ) {
return {
restrict : 'AC' ,
2019-01-22 10:54:03 +01:00
link : function ( scope , element /*, attrs */ ) {
2018-01-22 13:01:38 -08:00
element . bind ( 'click' , function ( ) {
var selection = window . getSelection ( ) ;
var range = document . createRange ( ) ;
range . selectNodeContents ( this ) ;
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
} ) ;
}
} ;
} ) ;
app . directive ( 'ngClickReveal' , function ( ) {
return {
restrict : 'A' ,
link : function ( scope , element , attrs ) {
element . addClass ( 'hand' ) ;
var value = '' ;
scope . $watch ( attrs . ngClickReveal , function ( newValue , oldValue ) {
if ( newValue !== oldValue ) {
element . html ( '<i>hidden</i>' ) ;
value = newValue ;
}
} ) ;
element . bind ( 'click' , function ( ) {
element . text ( value ) ;
} ) ;
}
} ;
} ) ;
// https://codepen.io/webmatze/pen/isuHh
app . directive ( 'tagInput' , function ( ) {
return {
restrict : 'E' ,
scope : {
inputTags : '=taglist'
} ,
2020-03-25 06:13:44 +01:00
require : '^form' ,
link : function ( $scope , element , attrs , formCtrl ) {
2018-01-22 13:01:38 -08:00
$scope . defaultWidth = 200 ;
$scope . tagText = '' ; // current tag being edited
$scope . placeholder = attrs . placeholder ;
$scope . tagArray = function ( ) {
if ( $scope . inputTags === undefined ) {
return [ ] ;
}
2020-03-28 16:46:06 -07:00
return $scope . inputTags . split ( ' ' ) . filter ( function ( tag ) {
2018-01-22 13:01:38 -08:00
return tag !== '' ;
} ) ;
} ;
$scope . addTag = function ( ) {
2020-03-25 06:51:39 +01:00
var tagArray = $scope . tagArray ( ) ;
// prevent adding empty or existing items
if ( $scope . tagText . length === 0 || tagArray . indexOf ( $scope . tagText ) !== - 1 ) {
return $scope . tagText = '' ;
2018-01-22 13:01:38 -08:00
}
2020-03-25 06:51:39 +01:00
2018-01-22 13:01:38 -08:00
tagArray . push ( $scope . tagText ) ;
2020-03-28 16:46:06 -07:00
$scope . inputTags = tagArray . join ( ' ' ) ;
2018-01-22 13:01:38 -08:00
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 ) ;
}
}
2020-03-25 06:13:44 +01:00
formCtrl . $setDirty ( ) ;
2020-03-28 16:46:06 -07:00
return $scope . inputTags = tagArray . join ( ' ' ) ;
2018-01-22 13:01:38 -08:00
} ;
$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 ( ) ;
}
} ) ;
2020-03-25 06:31:31 +01:00
element . bind ( 'click' , function ( ) {
element [ 0 ] . firstChild . lastChild . focus ( ) ;
} ) ;
2018-01-22 13:01:38 -08:00
element . bind ( 'keydown' , function ( e ) {
var key = e . which ;
if ( key === 9 || key === 13 ) {
e . preventDefault ( ) ;
}
if ( key === 8 ) {
return $scope . $apply ( 'deleteTag()' ) ;
}
} ) ;
element . bind ( 'keyup' , function ( e ) {
var key = e . which ;
2020-03-28 16:46:06 -07:00
if ( key === 9 || key === 13 || key === 32 ) {
2018-01-22 13:01:38 -08:00
e . preventDefault ( ) ;
return $scope . $apply ( 'addTag()' ) ;
}
} ) ;
} ,
template :
'<div class="tag-input-container">' +
2020-03-25 06:48:45 +01:00
'<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
'<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
'<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">×</button>' +
2018-01-22 13:01:38 -08:00
'</div>' +
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
'</div>'
} ;
} ) ;
app . config ( [ 'fitTextConfigProvider' , function ( fitTextConfigProvider ) {
fitTextConfigProvider . config = {
loadDelay : 250 ,
compressor : 0.9 ,
min : 8 ,
max : 24
} ;
} ] ) ;