Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/passman.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorbrantje <brantje@gmail.com>2016-10-24 11:53:59 +0300
committerbrantje <brantje@gmail.com>2016-10-24 20:56:04 +0300
commit8fab6a8725f49834bdb93eb96679f9b6cca0224f (patch)
treef6e470f4bbd4f7a98c8862a0d2bd6e1e1a14f8db /js
parent74f4486b6367eb431e9af1bf539f639a57c9db31 (diff)
Custom field types
Custom fields can have files. Order custom fields. It's now possible to reorganise the custom fields by drag and drop. Signed-off-by: brantje <brantje@gmail.com>
Diffstat (limited to 'js')
-rw-r--r--js/app/app.js9
-rw-r--r--js/app/app_public.js1
-rw-r--r--js/app/controllers/bookmarklet.js48
-rw-r--r--js/app/controllers/edit_credential.js65
-rw-r--r--js/templates.js6
-rw-r--r--js/vendor/download.js6
-rw-r--r--js/vendor/ui-sortable/sortable.js504
7 files changed, 617 insertions, 22 deletions
diff --git a/js/app/app.js b/js/app/app.js
index 04203dce..54e80a04 100644
--- a/js/app/app.js
+++ b/js/app/app.js
@@ -46,7 +46,8 @@
'ngclipboard',
'xeditable',
'ngTagsInput',
- 'angularjs-datetime-picker'
+ 'angularjs-datetime-picker',
+ 'ui.sortable'
])
.config(function ($routeProvider) {
$routeProvider
@@ -147,12 +148,14 @@
$('#controls').css('min-width', controlsWidth + magic);
}
};
+ /*
$(window).resize(adjustControlsWidth, 400);
setTimeout(function () {
adjustControlsWidth(true);
- }, 200);
+ }, 200);*/
//@Fack this
+ /*
$(document).on('click', '#app-navigation-toggle', function () {
setTimeout(function () {
if ($('#app-content').css('transform').toString().indexOf('matrix') >= 0) {
@@ -164,6 +167,6 @@
$('#passman-controls').css('width', '100%');
}
}, 350);
- });
+ });*/
});
}()); \ No newline at end of file
diff --git a/js/app/app_public.js b/js/app/app_public.js
index 319c8d46..9cf3c87b 100644
--- a/js/app/app_public.js
+++ b/js/app/app_public.js
@@ -32,6 +32,7 @@
'ngSanitize',
'ngTouch',
'ngclipboard',
+ 'ui.sortable'
]).config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common.requesttoken = oc_requesttoken;
diff --git a/js/app/controllers/bookmarklet.js b/js/app/controllers/bookmarklet.js
index b137beb1..7696cb0a 100644
--- a/js/app/controllers/bookmarklet.js
+++ b/js/app/controllers/bookmarklet.js
@@ -274,25 +274,61 @@
var _customField = {
label: '',
value: '',
- secret: false
+ secret: false,
+ field_type: 'text'
};
+ $scope.selected_field_type = 'text';
$scope.new_custom_field = angular.copy(_customField);
$scope.addCustomField = function () {
- if (!$scope.new_custom_field.label) {
+ var _field = angular.copy($scope.new_custom_field);
+
+ if (!_field.label) {
NotificationService.showNotification('Please fill in a label', 3000);
}
- if (!$scope.new_custom_field.value) {
+ if (!_field.value) {
NotificationService.showNotification('Please fill in a value!', 3000);
}
- if (!$scope.new_custom_field.label || !$scope.new_custom_field.value) {
+ if (!_field.label || !_field.value) {
return;
}
- $scope.storedCredential.custom_fields.push(angular.copy($scope.new_custom_field));
- $scope.new_custom_field = angular.copy(_customField);
+ $scope.selected_field_type = 'text';
+
+ _field.secret = angular.copy(($scope.selected_field_type === 'password'));
+ _field.field_type = angular.copy($scope.selected_field_type);
+ if(_field.field_type === 'file'){
+ var _file = $scope.new_custom_field.value;
+ FileService.uploadFile(_file).then(function (result) {
+ delete result.file_data;
+ result.filename = EncryptService.decryptString(result.filename);
+ _field.value = result;
+ $scope.storedCredential.custom_fields.push(_field);
+ $scope.new_custom_field = angular.copy(_customField);
+ });
+ } else {
+ $scope.storedCredential.custom_fields.push(_field);
+ $scope.new_custom_field = angular.copy(_customField);
+ }
+
+ };
+
+ $scope.addFileToCustomField = function (file) {
+ var _file = {
+ filename: file.name,
+ size: file.size,
+ mimetype: file.type,
+ data: file.data
+ };
+ $scope.new_custom_field.value = _file;
+ $scope.$digest();
};
$scope.deleteCustomField = function (field) {
+ if(field.hasOwnProperty('field_type')) {
+ if (field.field_type === 'file') {
+ FileService.deleteFile(field.value);
+ }
+ }
var idx = $scope.storedCredential.custom_fields.indexOf(field);
$scope.storedCredential.custom_fields.splice(idx, 1);
};
diff --git a/js/app/controllers/edit_credential.js b/js/app/controllers/edit_credential.js
index 3a59f9c6..d4015c7c 100644
--- a/js/app/controllers/edit_credential.js
+++ b/js/app/controllers/edit_credential.js
@@ -131,28 +131,77 @@
$scope.storedCredential.password_repeat = pass;
};
+
var _customField = {
label: '',
value: '',
- secret: false
+ secret: false,
+ field_type: 'text'
};
+ $scope.selected_field_type = 'text';
$scope.new_custom_field = angular.copy(_customField);
$scope.addCustomField = function () {
- if (!$scope.new_custom_field.label) {
+ var _field = angular.copy($scope.new_custom_field);
+
+ if (!_field.label) {
NotificationService.showNotification('Please fill in a label', 3000);
}
- if (!$scope.new_custom_field.value) {
+ if (!_field.value) {
NotificationService.showNotification('Please fill in a value!', 3000);
}
- if (!$scope.new_custom_field.label || !$scope.new_custom_field.value) {
+ if (!_field.label || !_field.value) {
return;
}
- $scope.storedCredential.custom_fields.push(angular.copy($scope.new_custom_field));
- $scope.new_custom_field = angular.copy(_customField);
+ $scope.selected_field_type = 'text';
+
+ _field.secret = angular.copy(($scope.selected_field_type === 'password'));
+ _field.field_type = angular.copy($scope.selected_field_type);
+ if(_field.field_type === 'file'){
+ var key = false;
+ var _file = $scope.new_custom_field.value;
+ if (!$scope.storedCredential.hasOwnProperty('acl') && $scope.storedCredential.hasOwnProperty('shared_key')) {
+
+ if ($scope.storedCredential.shared_key) {
+ key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key));
+ }
+ }
+
+ if ($scope.storedCredential.hasOwnProperty('acl')) {
+ key = EncryptService.decryptString(angular.copy($scope.storedCredential.acl.shared_key));
+ }
+
+ FileService.uploadFile(_file, key).then(function (result) {
+ delete result.file_data;
+ result.filename = EncryptService.decryptString(result.filename, key);
+ _field.value = result;
+ $scope.storedCredential.custom_fields.push(_field);
+ $scope.new_custom_field = angular.copy(_customField);
+ });
+ } else {
+ $scope.storedCredential.custom_fields.push(_field);
+ $scope.new_custom_field = angular.copy(_customField);
+ }
+
+ };
+
+ $scope.addFileToCustomField = function (file) {
+ var _file = {
+ filename: file.name,
+ size: file.size,
+ mimetype: file.type,
+ data: file.data
+ };
+ $scope.new_custom_field.value = _file;
+ $scope.$digest();
};
$scope.deleteCustomField = function (field) {
+ if(field.hasOwnProperty('field_type')) {
+ if (field.field_type === 'file') {
+ FileService.deleteFile(field.value);
+ }
+ }
var idx = $scope.storedCredential.custom_fields.indexOf(field);
$scope.storedCredential.custom_fields.splice(idx, 1);
};
@@ -280,7 +329,9 @@
delete _credential.shared_key;
var _useKey = (key != null);
var regex = /(<([^>]+)>)/ig;
- _credential.description = _credential.description.replace(regex, "");
+ if(_credential.description) {
+ _credential.description = _credential.description.replace(regex, "");
+ }
CredentialService.updateCredential(_credential, _useKey).then(function () {
SettingsService.setSetting('edit_credential', null);
$location.path('/vault/' + $routeParams.vault_id);
diff --git a/js/templates.js b/js/templates.js
index 463698d6..dadfc7f4 100644
--- a/js/templates.js
+++ b/js/templates.js
@@ -21,13 +21,13 @@ angular.module('views/partials/forms/edit_credential/basics.html', []).run(['$te
angular.module('views/partials/forms/edit_credential/custom_fields.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/partials/forms/edit_credential/custom_fields.html',
- '<div class="row"><div class="col-xs-4"><label>Field label</label><input type="text" ng-model="new_custom_field.label"></div><div class="col-xs-4"><label>Field value</label><input type="text" ng-model="new_custom_field.value" ng-show="!new_custom_field.secret"> <input type="password" ng-model="new_custom_field.value" ng-show="new_custom_field.secret"><label><input type="checkbox" ng-model="new_custom_field.secret">Confidential</label></div><div class="col-xs-2"><label class="invisible">Add</label><button ng-click="addCustomField()">+</button></div></div><div class="row custom_fields" ng-if="storedCredential.custom_fields.length > 0"><div class="col-xs-12 table"><table><thead><tr use-theme><th class="field_label">Label</th><th class="field_value">Value</th><th class="field_secret">Confidential</th><th class="field_actions">Actions</th></tr></thead><tr ng-repeat="field in storedCredential.custom_fields"><td><a href="#" editable-text="field.label">{{ field.label || "empty" }}</a></td><td><span ng-if="!field.secret"><a href="#" editable-text="field.value">{{ field.value || "empty" }}</a></span> <span ng-if="field.secret"><a href="#" editable-password="field.value"><span ng-repeat="n in [] | range:field.value.length">*</span></a></span> <input ng-model="field.value" type="text" ng-show="edit && !secret"> <input ng-model="field.value" type="text" ng-show="edit && secret"></td><td><input type="checkbox" ng-model="field.secret"></td><td class="field_actions"><i class="fa fa-trash" ng-click="deleteCustomField(field)"></i></td></tr></table></div></div>');
+ '<div class="row"><div class="col-xs-12 col-md-4"><label>Field label</label><input type="text" ng-model="new_custom_field.label"></div><div class="col-xs-10 col-md-6 field-value"><div class="row"><div class="col-xs-12"><label>Field value</label></div></div><div class="row"><div class="col-xs-8 valueInput"><input type="text" ng-model="new_custom_field.value" ng-show="selected_field_type === \'text\'"><password-gen ng-model="new_custom_field.value" ng-show="selected_field_type ===\'password\'" settings="{generateOnCreate: false }"></password-gen><span ng-show="selected_field_type ===\'file\'"><input id="custom_field_file" class="inputfile" type="file" file-select success="addFileToCustomField" error="fileLoadError" progress="fileSelectProgress"><label for="custom_field_file"><i class="fa fa-upload" aria-hidden="true"></i> {{ new_custom_field.value.filename || \'Choose a file\'}}</label></span></div><div class="col-xs-4 selectType"><select class="form-control" ng-model="$parent.selected_field_type"><option value="text">Text</option><option value="password">Password</option><option value="file">File</option></select></div></div><div class="row"><div class="col-xs-12"><ng-password-meter ng-if="selected_field_type ===\'password\'" password="new_custom_field.value"></ng-password-meter></div></div></div><div class="col-xs-2 col-md-2"><label class="invisible">Add</label><button ng-click="addCustomField()">+</button></div></div><div class="row custom_fields" ng-if="storedCredential.custom_fields.length > 0"><div class="col-xs-12 table"><table><thead><tr use-theme><td class="dragger"></td><th class="field_label">Label</th><th class="field_value">Value</th><th class="field_secret">Type</th><th class="field_actions">Actions</th></tr></thead><tbody ui-sortable ng-model="storedCredential.custom_fields"><tr ng-repeat="field in storedCredential.custom_fields"><td class="dragger"><i class="fa fa-arrows-v"></i></td><td><a href="#" editable-text="field.label">{{ field.label || "empty" }}</a></td><td><span ng-if="field.field_type === \'text\'"><a href="#" editable-text="field.value">{{ field.value || "empty" }}</a></span> <span ng-if="field.field_type === \'password\'"><a href="#" editable-password="field.value"><span ng-repeat="n in [] | range:field.value.length">*</span></a></span> <span ng-if="field.field_type === \'file\'">{{field.value.filename}} ({{field.value.size | bytes}})</span></td><td>{{ field.field_type }}</td><td class="field_actions"><i class="fa fa-trash" ng-click="deleteCustomField(field)"></i></td></tr></tbody></table></div></div>');
}]);
angular.module('views/partials/forms/edit_credential/files.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/partials/forms/edit_credential/files.html',
- '<div class="row file_tab"><div class="col-xs-12 col-md-6"><input type="file" file-select success="fileLoaded" error="fileLoadError" progress="fileSelectProgress"> <span ng-if="fileprogress.file_percent > 0"><div progress-bar="fileprogress.file_percent"></div></span></div></div><div class="row files" ng-if="storedCredential.files.length > 0"><div class="col-xs-12 table"><table><thead use-theme><tr><th class="field_label">Filename</th><th class="field_value">Upload date</th><th class="field_secret">Size</th><th class="field_actions">Actions</th></tr></thead><tr ng-repeat="file in storedCredential.files"><td><a href="#" editable-text="file.filename">{{ file.filename || "empty" }}</a></td><td>{{file.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td><td>{{file.size | bytes}}</td><td class="field_actions"><i class="fa fa-trash" ng-click="deleteFile(file)"></i></td></tr></table></div></div>');
+ '<div class="row file_tab"><div class="col-xs-12 col-md-6"><input class="inputfile" id="file" type="file" file-select success="fileLoaded" error="fileLoadError" progress="fileSelectProgress"><label for="file"><i class="fa fa-upload" aria-hidden="true"></i> Choose a file</label><span ng-if="fileprogress.file_percent > 0"><div progress-bar="fileprogress.file_percent"></div></span></div></div><div class="row files" ng-if="storedCredential.files.length > 0"><div class="col-xs-12 table"><table><thead use-theme><tr><th class="field_label">Filename</th><th class="field_value">Upload date</th><th class="field_secret">Size</th><th class="field_actions">Actions</th></tr></thead><tr ng-repeat="file in storedCredential.files"><td><a href="#" editable-text="file.filename">{{ file.filename || "empty" }}</a></td><td>{{file.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td><td>{{file.size | bytes}}</td><td class="field_actions"><i class="fa fa-trash" ng-click="deleteFile(file)"></i></td></tr></table></div></div>');
}]);
angular.module('views/partials/forms/edit_credential/otp.html', []).run(['$templateCache', function($templateCache) {
@@ -113,7 +113,7 @@ angular.module('views/share_credential.html', []).run(['$templateCache', functio
angular.module('views/show_vault.html', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('views/show_vault.html',
- '<div off-click="closeSelected()" off-click-filter="\'.download-js-link, .sidebar-shown\'"><div id="passman-controls" ng-class="{ \'sidebar-shown\': selectedCredential }"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span><div class="actions creatable"><span ng-click="addCredential()" class="button new"><span>+</span></span></div><div class="title" ng-show="filtered_credentials.length > 0">Showing {{filtered_credentials.length}} of {{active_vault.credentials.length - 1}} credentials</div><div class="searchboxContainer"><input type="text" ng-model="filterOptions.filterText" class="searchbox" placeholder="Search credential..." select-on-click clear-btn></div><div class="viewModes"><div class="view-mode" ng-class="{\'active\': view_mode === \'list\' }" ng-click="switchViewMode(\'list\')"><i class="fa fa-list"></i></div><div class="view-mode" ng-class="{\'active\': view_mode === \'grid\' }" ng-click="switchViewMode(\'grid\')"><i class="fa fa-th-large"></i></div></div></div><div class="loaderContainer" ng-if="show_spinner"><div class="loader" use-theme type="\'border-bottom-color\'"></div></div><div ng-init="menuOpen = false;"><table class="credential-table" ng-if="view_mode === \'list\'"><tr ng-repeat="credential in filtered_credentials | orderBy:\'label\'" ng-if="showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{ ::tag.text}}</span></span> <span class="icon"><i class="fa fa-lock" ng-if="!credential.acl && !credential.shared_key"></i> <i class="fa fa-share-alt" ng-if="credential.acl"></i> <i class="fa fa-share-alt-square" ng-if="credential.shared_key"></i></span> <span class="label">{{ ::credential.label}}</span></td></tr></table><ul class="grid-view" ng-if="view_mode === \'grid\'"><li class="credential" ng-repeat="credential in filtered_credentials | orderBy:\'label\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" use-theme type="\'border-color\'"><div class="credential_content"><div class="label">{{ ::credential.label}}</div><div class="tags"><div class="tag" ng-repeat="tag in credential.tags_raw">{{ ::tag.text}}</div></div></div></li></ul></div><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><h2>{{selectedCredential.label}}</h2><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><div class="credential-data"><div class="row" ng-show="selectedCredential.username"><div class="col-xs-4 col-md-3 col-lg-3">Account</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.username"></span></div></div><div class="row" ng-show="selectedCredential.password"><div class="col-xs-4 col-md-3 col-lg-3">Password</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.password" secret="\'true\'"></span></div></div><div class="row" ng-show="selectedCredential.otp.secret"><div class="col-xs-4 col-md-3 col-lg-3">OTP</div><div class="col-xs-8 col-md-9 col-lg-9"><span otp-generator secret="selectedCredential.otp.secret"></span></div></div><div class="row" ng-show="selectedCredential.email"><div class="col-xs-4 col-md-3 col-lg-3">E-mail</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.email"></span></div></div><div class="row" ng-show="selectedCredential.url"><div class="col-xs-4 col-md-3 col-lg-3">URL</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.url"></span></div></div><div class="row" ng-show="selectedCredential.description"><div class="col-xs-4 col-md-3 col-lg-3">Notes</div><div class="col-xs-8 col-md-9 col-lg-9" ng-bind-html="selectedCredential.description_html"></div></div><div class="row" ng-show="selectedCredential.files.length > 0"><div class="col-xs-4 col-md-3 col-lg-3">Files</div><div class="col-xs-8 col-md-9 col-lg-9"><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(selectedCredential, file)">{{file.filename}} ({{file.size | bytes}})</div></div></div><div class="row" ng-repeat="field in selectedCredential.custom_fields"><div class="col-xs-4 col-md-3 col-lg-3">{{field.label}}</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="field.value" secret="field.secret"></span></div></div><div class="row" ng-show="selectedCredential.expire_time > 0"><div class="col-xs-4 col-md-3 col-lg-3">Expire time</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.expire_time * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row" ng-show="selectedCredential.changed"><div class="col-xs-4 col-md-3 col-lg-3">Changed</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row" ng-show="selectedCredential.created"><div class="col-xs-4 col-md-3 col-lg-3">Created</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row"><div class="col-xs-12"><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div></div></div></div><div ng-show="selectedCredential"><div><button class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-edit"></span> Edit</button> <button class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-trash"></span> Delete</button> <button class="button" ng-click="shareCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && selectedCredential.acl === undefined"><span class="fa fa-share"></span> Share</button> <button class="button" ng-click="getRevisions(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.HISTORY)"><span class="fa fa-undo"></span> Revisions</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential) && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-recycle"></span> Recover</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> Destroy</button></div></div></div></div><div class="share_popup" style="display: none">You have incoming share requests.<br>If you want to the credential in a other vault,<br>logout of this vault and login to the vault you want the shared credential in. {{active_vault.vault_id}}<table class="table"><thead><tr><td>Label</td><td>Permissions</td><td>Received from</td><td>Date</td></tr></thead><tr ng-repeat="share_request in incoming_share_requests" ng-if="share_request.target_vault_id == active_vault.vault_id"><td>{{share_request.credential_label}}</td><td>{{share_request.permissions}}</td><td>{{share_request.from_user_id}}</td><td>{{share_request.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td><td><span class="link" ng-click="acceptShareRequest(share_request)">Accept</span> | <span class="link" ng-click="declineShareRequest(share_request)">Decline</span></td></tr></table></div>');
+ '<div off-click="closeSelected()" off-click-filter="\'.download-js-link, .sidebar-shown\'"><div id="passman-controls" ng-class="{ \'sidebar-shown\': selectedCredential }"><div class="breadcrumb"><div class="breadcrumb"><div class="crumb svg ui-droppable" data-dir="/"><a><i class="fa fa-home"></i></a></div><div class="crumb svg last"><a>{{active_vault.name}}</a></div></div></div><span class="title" ng-if="delete_time">Showing deleted since: <span ng-if="delete_time == 1">All time</span> <span ng-if="delete_time > 1">{{delete_time | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</span></span><div class="actions creatable"><span ng-click="addCredential()" class="button new"><span>+</span></span></div><div class="title" ng-show="filtered_credentials.length > 0">Showing {{filtered_credentials.length}} of {{active_vault.credentials.length - 1}} credentials</div><div class="searchboxContainer"><input type="text" ng-model="filterOptions.filterText" class="searchbox" placeholder="Search credential..." select-on-click clear-btn></div><div class="viewModes"><div class="view-mode" ng-class="{\'active\': view_mode === \'list\' }" ng-click="switchViewMode(\'list\')"><i class="fa fa-list"></i></div><div class="view-mode" ng-class="{\'active\': view_mode === \'grid\' }" ng-click="switchViewMode(\'grid\')"><i class="fa fa-th-large"></i></div></div></div><div class="loaderContainer" ng-if="show_spinner"><div class="loader" use-theme type="\'border-bottom-color\'"></div></div><div ng-init="menuOpen = false;"><table class="credential-table" ng-if="view_mode === \'list\'"><tr ng-repeat="credential in filtered_credentials | orderBy:\'label\'" ng-if="showCredentialRow(credential)" ng-click="selectCredential(credential)" ng-class="{\'selected\': selectedCredential.credential_id == credential.credential_id}"><td><span class="tags"><span class="tag" ng-repeat="tag in credential.tags_raw">{{ ::tag.text}}</span></span> <span class="icon"><i class="fa fa-lock" ng-if="!credential.acl && !credential.shared_key"></i> <i class="fa fa-share-alt" ng-if="credential.acl"></i> <i class="fa fa-share-alt-square" ng-if="credential.shared_key"></i></span> <span class="label">{{ ::credential.label}}</span></td></tr></table><ul class="grid-view" ng-if="view_mode === \'grid\'"><li class="credential" ng-repeat="credential in filtered_credentials | orderBy:\'label\'" ng-if="credential.hidden == 0 && showCredentialRow(credential)" ng-click="selectCredential(credential)" use-theme type="\'border-color\'"><div class="credential_content"><div class="label">{{ ::credential.label}}</div><div class="tags"><div class="tag" ng-repeat="tag in credential.tags_raw">{{ ::tag.text}}</div></div></div></li></ul></div><div id="app-sidebar" class="detailsView scroll-container app_sidebar" ng-show="selectedCredential"><h2>{{selectedCredential.label}}</h2><span class="close icon-close" ng-click="closeSelected()" alt="Close"></span><div class="credential-data"><div class="row" ng-show="selectedCredential.username"><div class="col-xs-4 col-md-3 col-lg-3">Account</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.username"></span></div></div><div class="row" ng-show="selectedCredential.password"><div class="col-xs-4 col-md-3 col-lg-3">Password</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.password" secret="\'true\'"></span></div></div><div class="row" ng-show="selectedCredential.otp.secret"><div class="col-xs-4 col-md-3 col-lg-3">OTP</div><div class="col-xs-8 col-md-9 col-lg-9"><span otp-generator secret="selectedCredential.otp.secret"></span></div></div><div class="row" ng-show="selectedCredential.email"><div class="col-xs-4 col-md-3 col-lg-3">E-mail</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.email"></span></div></div><div class="row" ng-show="selectedCredential.url"><div class="col-xs-4 col-md-3 col-lg-3">URL</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="selectedCredential.url"></span></div></div><div class="row" ng-show="selectedCredential.description"><div class="col-xs-4 col-md-3 col-lg-3">Notes</div><div class="col-xs-8 col-md-9 col-lg-9" ng-bind-html="selectedCredential.description_html"></div></div><div class="row" ng-show="selectedCredential.files.length > 0"><div class="col-xs-4 col-md-3 col-lg-3">Files</div><div class="col-xs-8 col-md-9 col-lg-9"><div ng-repeat="file in selectedCredential.files" class="link" ng-click="downloadFile(selectedCredential, file)">{{file.filename}} ({{file.size | bytes}})</div></div></div><div class="row" ng-repeat="field in selectedCredential.custom_fields"><div class="col-xs-4 col-md-3 col-lg-3">{{field.label}}</div><div class="col-xs-8 col-md-9 col-lg-9"><span credential-field value="field.value" secret="field.secret" ng-if="field.field_type !== \'file\' || !field.field_type"></span> <span ng-if="field.field_type === \'file\'" class="link" ng-click="downloadFile(selectedCredential, field.value)">{{field.value.filename}} ({{field.value.size | bytes}})</span></div></div><div class="row" ng-show="selectedCredential.expire_time > 0"><div class="col-xs-4 col-md-3 col-lg-3">Expire time</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.expire_time * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row" ng-show="selectedCredential.changed"><div class="col-xs-4 col-md-3 col-lg-3">Changed</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.changed * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row" ng-show="selectedCredential.created"><div class="col-xs-4 col-md-3 col-lg-3">Created</div><div class="col-xs-8 col-md-9 col-lg-9">{{selectedCredential.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</div></div><div class="row"><div class="col-xs-12"><div class="tags"><span class="tag" ng-repeat="tag in selectedCredential.tags">{{tag.text}}</span></div></div></div></div><div ng-show="selectedCredential"><div><button class="button" ng-click="editCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-edit"></span> Edit</button> <button class="button" ng-click="deleteCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-trash"></span> Delete</button> <button class="button" ng-click="shareCredential(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && selectedCredential.acl === undefined"><span class="fa fa-share"></span> Share</button> <button class="button" ng-click="getRevisions(selectedCredential)" ng-if="selectedCredential.delete_time == 0 && hasPermission(selectedCredential.acl.permissions, permissions.permissions.HISTORY)"><span class="fa fa-undo"></span> Revisions</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="recoverCredential(selectedCredential) && hasPermission(selectedCredential.acl.permissions, permissions.permissions.WRITE)"><span class="fa fa-recycle"></span> Recover</button> <button class="button" ng-if="selectedCredential.delete_time > 0" ng-click="destroyCredential(selectedCredential)"><span class="fa fa-bomb"></span> Destroy</button></div></div></div></div><div class="share_popup" style="display: none">You have incoming share requests.<br>If you want to the credential in a other vault,<br>logout of this vault and login to the vault you want the shared credential in. {{active_vault.vault_id}}<table class="table"><thead><tr><td>Label</td><td>Permissions</td><td>Received from</td><td>Date</td></tr></thead><tr ng-repeat="share_request in incoming_share_requests" ng-if="share_request.target_vault_id == active_vault.vault_id"><td>{{share_request.credential_label}}</td><td>{{share_request.permissions}}</td><td>{{share_request.from_user_id}}</td><td>{{share_request.created * 1000 | date:\'dd-MM-yyyy @ HH:mm:ss\'}}</td><td><span class="link" ng-click="acceptShareRequest(share_request)">Accept</span> | <span class="link" ng-click="declineShareRequest(share_request)">Decline</span></td></tr></table></div>');
}]);
angular.module('views/vaults.html', []).run(['$templateCache', function($templateCache) {
diff --git a/js/vendor/download.js b/js/vendor/download.js
index 3bf0c449..43aa8602 100644
--- a/js/vendor/download.js
+++ b/js/vendor/download.js
@@ -6,7 +6,7 @@
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
// https://github.com/rndme/download
-/*
+
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
@@ -158,5 +158,5 @@
reader.readAsDataURL(blob);
}
return true;
- }; /* end download()
-}));*/ \ No newline at end of file
+ }; /* end download()*/
+})); \ No newline at end of file
diff --git a/js/vendor/ui-sortable/sortable.js b/js/vendor/ui-sortable/sortable.js
new file mode 100644
index 00000000..7c799d15
--- /dev/null
+++ b/js/vendor/ui-sortable/sortable.js
@@ -0,0 +1,504 @@
+/*
+ jQuery UI Sortable plugin wrapper
+
+ @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
+ */
+angular.module('ui.sortable', [])
+ .value('uiSortableConfig',{
+ // the default for jquery-ui sortable is "> *", we need to restrict this to
+ // ng-repeat items
+ // if the user uses
+ items: '> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]'
+ })
+ .directive('uiSortable', [
+ 'uiSortableConfig', '$timeout', '$log',
+ function(uiSortableConfig, $timeout, $log) {
+ return {
+ require: '?ngModel',
+ scope: {
+ ngModel: '=',
+ uiSortable: '='
+ },
+ link: function(scope, element, attrs, ngModel) {
+ var savedNodes;
+
+ function combineCallbacks(first, second){
+ var firstIsFunc = typeof first === 'function';
+ var secondIsFunc = typeof second === 'function';
+ if(firstIsFunc && secondIsFunc) {
+ return function() {
+ first.apply(this, arguments);
+ second.apply(this, arguments);
+ };
+ } else if (secondIsFunc) {
+ return second;
+ }
+ return first;
+ }
+
+ function getSortableWidgetInstance(element) {
+ // this is a fix to support jquery-ui prior to v1.11.x
+ // otherwise we should be using `element.sortable('instance')`
+ var data = element.data('ui-sortable');
+ if (data && typeof data === 'object' && data.widgetFullName === 'ui-sortable') {
+ return data;
+ }
+ return null;
+ }
+
+ function patchSortableOption(key, value) {
+ if (callbacks[key]) {
+ if( key === 'stop' ){
+ // call apply after stop
+ value = combineCallbacks(
+ value, function() { scope.$apply(); });
+
+ value = combineCallbacks(value, afterStop);
+ }
+ // wrap the callback
+ value = combineCallbacks(callbacks[key], value);
+ } else if (wrappers[key]) {
+ value = wrappers[key](value);
+ }
+
+ // patch the options that need to have values set
+ if (!value && (key === 'items' || key === 'ui-model-items')) {
+ value = uiSortableConfig.items;
+ }
+
+ return value;
+ }
+
+ function patchUISortableOptions(newVal, oldVal, sortableWidgetInstance) {
+ function addDummyOptionKey(value, key) {
+ if (!(key in opts)) {
+ // add the key in the opts object so that
+ // the patch function detects and handles it
+ opts[key] = null;
+ }
+ }
+ // for this directive to work we have to attach some callbacks
+ angular.forEach(callbacks, addDummyOptionKey);
+
+ // only initialize it in case we have to
+ // update some options of the sortable
+ var optsDiff = null;
+
+ if (oldVal) {
+ // reset deleted options to default
+ var defaultOptions;
+ angular.forEach(oldVal, function(oldValue, key) {
+ if (!newVal || !(key in newVal)) {
+ if (key in directiveOpts) {
+ if (key === 'ui-floating') {
+ opts[key] = 'auto';
+ } else {
+ opts[key] = patchSortableOption(key, undefined);
+ }
+ return;
+ }
+
+ if (!defaultOptions) {
+ defaultOptions = angular.element.ui.sortable().options;
+ }
+ var defaultValue = defaultOptions[key];
+ defaultValue = patchSortableOption(key, defaultValue);
+
+ if (!optsDiff) {
+ optsDiff = {};
+ }
+ optsDiff[key] = defaultValue;
+ opts[key] = defaultValue;
+ }
+ });
+ }
+
+ // update changed options
+ angular.forEach(newVal, function(value, key) {
+ // if it's a custom option of the directive,
+ // handle it approprietly
+ if (key in directiveOpts) {
+ if (key === 'ui-floating' && (value === false || value === true) && sortableWidgetInstance) {
+ sortableWidgetInstance.floating = value;
+ }
+
+ opts[key] = patchSortableOption(key, value);
+ return;
+ }
+
+ value = patchSortableOption(key, value);
+
+ if (!optsDiff) {
+ optsDiff = {};
+ }
+ optsDiff[key] = value;
+ opts[key] = value;
+ });
+
+ return optsDiff;
+ }
+
+ function getPlaceholderElement (element) {
+ var placeholder = element.sortable('option','placeholder');
+
+ // placeholder.element will be a function if the placeholder, has
+ // been created (placeholder will be an object). If it hasn't
+ // been created, either placeholder will be false if no
+ // placeholder class was given or placeholder.element will be
+ // undefined if a class was given (placeholder will be a string)
+ if (placeholder && placeholder.element && typeof placeholder.element === 'function') {
+ var result = placeholder.element();
+ // workaround for jquery ui 1.9.x,
+ // not returning jquery collection
+ result = angular.element(result);
+ return result;
+ }
+ return null;
+ }
+
+ function getPlaceholderExcludesludes (element, placeholder) {
+ // exact match with the placeholder's class attribute to handle
+ // the case that multiple connected sortables exist and
+ // the placeholder option equals the class of sortable items
+ var notCssSelector = opts['ui-model-items'].replace(/[^,]*>/g, '');
+ var excludes = element.find('[class="' + placeholder.attr('class') + '"]:not(' + notCssSelector + ')');
+ return excludes;
+ }
+
+ function hasSortingHelper (element, ui) {
+ var helperOption = element.sortable('option','helper');
+ return helperOption === 'clone' || (typeof helperOption === 'function' && ui.item.sortable.isCustomHelperUsed());
+ }
+
+ function getSortingHelper (element, ui, savedNodes) {
+ var result = null;
+ if (hasSortingHelper(element, ui) &&
+ element.sortable( 'option', 'appendTo' ) === 'parent') {
+ // The .ui-sortable-helper element (that's the default class name)
+ // is placed last.
+ result = savedNodes.last();
+ }
+ return result;
+ }
+
+ // thanks jquery-ui
+ function isFloating (item) {
+ return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display'));
+ }
+
+ function getElementScope(elementScopes, element) {
+ var result = null;
+ for (var i = 0; i < elementScopes.length; i++) {
+ var x = elementScopes[i];
+ if (x.element[0] === element[0]) {
+ result = x.scope;
+ break;
+ }
+ }
+ return result;
+ }
+
+ function afterStop(e, ui) {
+ ui.item.sortable._destroy();
+ }
+
+ // return the index of ui.item among the items
+ // we can't just do ui.item.index() because there it might have siblings
+ // which are not items
+ function getItemIndex(item) {
+ return item.parent()
+ .find(opts['ui-model-items'])
+ .index(item);
+ }
+
+ var opts = {};
+
+ // directive specific options
+ var directiveOpts = {
+ 'ui-floating': undefined,
+ 'ui-model-items': uiSortableConfig.items
+ };
+
+ var callbacks = {
+ receive: null,
+ remove: null,
+ start: null,
+ stop: null,
+ update: null
+ };
+
+ var wrappers = {
+ helper: null
+ };
+
+ angular.extend(opts, directiveOpts, uiSortableConfig, scope.uiSortable);
+
+ if (!angular.element.fn || !angular.element.fn.jquery) {
+ $log.error('ui.sortable: jQuery should be included before AngularJS!');
+ return;
+ }
+
+ function wireUp () {
+ // When we add or remove elements, we need the sortable to 'refresh'
+ // so it can find the new/removed elements.
+ scope.$watchCollection('ngModel', function() {
+ // Timeout to let ng-repeat modify the DOM
+ $timeout(function() {
+ // ensure that the jquery-ui-sortable widget instance
+ // is still bound to the directive's element
+ if (!!getSortableWidgetInstance(element)) {
+ element.sortable('refresh');
+ }
+ }, 0, false);
+ });
+
+ callbacks.start = function(e, ui) {
+ if (opts['ui-floating'] === 'auto') {
+ // since the drag has started, the element will be
+ // absolutely positioned, so we check its siblings
+ var siblings = ui.item.siblings();
+ var sortableWidgetInstance = getSortableWidgetInstance(angular.element(e.target));
+ sortableWidgetInstance.floating = isFloating(siblings);
+ }
+
+ // Save the starting position of dragged item
+ var index = getItemIndex(ui.item);
+ ui.item.sortable = {
+ model: ngModel.$modelValue[index],
+ index: index,
+ source: ui.item.parent(),
+ sourceModel: ngModel.$modelValue,
+ cancel: function () {
+ ui.item.sortable._isCanceled = true;
+ },
+ isCanceled: function () {
+ return ui.item.sortable._isCanceled;
+ },
+ isCustomHelperUsed: function () {
+ return !!ui.item.sortable._isCustomHelperUsed;
+ },
+ _isCanceled: false,
+ _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed,
+ _destroy: function () {
+ angular.forEach(ui.item.sortable, function(value, key) {
+ ui.item.sortable[key] = undefined;
+ });
+ }
+ };
+ };
+
+ callbacks.activate = function(e, ui) {
+ // We need to make a copy of the current element's contents so
+ // we can restore it after sortable has messed it up.
+ // This is inside activate (instead of start) in order to save
+ // both lists when dragging between connected lists.
+ savedNodes = element.contents();
+
+ // If this list has a placeholder (the connected lists won't),
+ // don't inlcude it in saved nodes.
+ var placeholder = getPlaceholderElement(element);
+ if (placeholder && placeholder.length) {
+ var excludes = getPlaceholderExcludesludes(element, placeholder);
+ savedNodes = savedNodes.not(excludes);
+ }
+
+ // save the directive's scope so that it is accessible from ui.item.sortable
+ var connectedSortables = ui.item.sortable._connectedSortables || [];
+
+ connectedSortables.push({
+ element: element,
+ scope: scope
+ });
+
+ ui.item.sortable._connectedSortables = connectedSortables;
+ };
+
+ callbacks.update = function(e, ui) {
+ // Save current drop position but only if this is not a second
+ // update that happens when moving between lists because then
+ // the value will be overwritten with the old value
+ if(!ui.item.sortable.received) {
+ ui.item.sortable.dropindex = getItemIndex(ui.item);
+ var droptarget = ui.item.parent();
+ ui.item.sortable.droptarget = droptarget;
+
+ var droptargetScope = getElementScope(ui.item.sortable._connectedSortables, droptarget);
+ ui.item.sortable.droptargetModel = droptargetScope.ngModel;
+
+ // Cancel the sort (let ng-repeat do the sort for us)
+ // Don't cancel if this is the received list because it has
+ // already been canceled in the other list, and trying to cancel
+ // here will mess up the DOM.
+ element.sortable('cancel');
+ }
+
+ // Put the nodes back exactly the way they started (this is very
+ // important because ng-repeat uses comment elements to delineate
+ // the start and stop of repeat sections and sortable doesn't
+ // respect their order (even if we cancel, the order of the
+ // comments are still messed up).
+ var sortingHelper = !ui.item.sortable.received && getSortingHelper(element, ui, savedNodes);
+ if (sortingHelper && sortingHelper.length) {
+ // Restore all the savedNodes except from the sorting helper element.
+ // That way it will be garbage collected.
+ savedNodes = savedNodes.not(sortingHelper);
+ }
+ savedNodes.appendTo(element);
+
+ // If this is the target connected list then
+ // it's safe to clear the restored nodes since:
+ // update is currently running and
+ // stop is not called for the target list.
+ if(ui.item.sortable.received) {
+ savedNodes = null;
+ }
+
+ // If received is true (an item was dropped in from another list)
+ // then we add the new item to this list otherwise wait until the
+ // stop event where we will know if it was a sort or item was
+ // moved here from another list
+ if(ui.item.sortable.received && !ui.item.sortable.isCanceled()) {
+ scope.$apply(function () {
+ ngModel.$modelValue.splice(ui.item.sortable.dropindex, 0,
+ ui.item.sortable.moved);
+ });
+ }
+ };
+
+ callbacks.stop = function(e, ui) {
+ // If the received flag hasn't be set on the item, this is a
+ // normal sort, if dropindex is set, the item was moved, so move
+ // the items in the list.
+ if(!ui.item.sortable.received &&
+ ('dropindex' in ui.item.sortable) &&
+ !ui.item.sortable.isCanceled()) {
+
+ scope.$apply(function () {
+ ngModel.$modelValue.splice(
+ ui.item.sortable.dropindex, 0,
+ ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]);
+ });
+ } else {
+ // if the item was not moved, then restore the elements
+ // so that the ngRepeat's comment are correct.
+ if ((!('dropindex' in ui.item.sortable) || ui.item.sortable.isCanceled()) &&
+ !angular.equals(element.contents(), savedNodes)) {
+
+ var sortingHelper = getSortingHelper(element, ui, savedNodes);
+ if (sortingHelper && sortingHelper.length) {
+ // Restore all the savedNodes except from the sorting helper element.
+ // That way it will be garbage collected.
+ savedNodes = savedNodes.not(sortingHelper);
+ }
+ savedNodes.appendTo(element);
+ }
+ }
+
+ // It's now safe to clear the savedNodes
+ // since stop is the last callback.
+ savedNodes = null;
+ };
+
+ callbacks.receive = function(e, ui) {
+ // An item was dropped here from another list, set a flag on the
+ // item.
+ ui.item.sortable.received = true;
+ };
+
+ callbacks.remove = function(e, ui) {
+ // Workaround for a problem observed in nested connected lists.
+ // There should be an 'update' event before 'remove' when moving
+ // elements. If the event did not fire, cancel sorting.
+ if (!('dropindex' in ui.item.sortable)) {
+ element.sortable('cancel');
+ ui.item.sortable.cancel();
+ }
+
+ // Remove the item from this list's model and copy data into item,
+ // so the next list can retrive it
+ if (!ui.item.sortable.isCanceled()) {
+ scope.$apply(function () {
+ ui.item.sortable.moved = ngModel.$modelValue.splice(
+ ui.item.sortable.index, 1)[0];
+ });
+ }
+ };
+
+ wrappers.helper = function (inner) {
+ if (inner && typeof inner === 'function') {
+ return function (e, item) {
+ var oldItemSortable = item.sortable;
+ var index = getItemIndex(item);
+ item.sortable = {
+ model: ngModel.$modelValue[index],
+ index: index,
+ source: item.parent(),
+ sourceModel: ngModel.$modelValue,
+ _restore: function () {
+ angular.forEach(item.sortable, function(value, key) {
+ item.sortable[key] = undefined;
+ });
+
+ item.sortable = oldItemSortable;
+ }
+ };
+
+ var innerResult = inner.apply(this, arguments);
+ item.sortable._restore();
+ item.sortable._isCustomHelperUsed = item !== innerResult;
+ return innerResult;
+ };
+ }
+ return inner;
+ };
+
+ scope.$watchCollection('uiSortable', function(newVal, oldVal) {
+ // ensure that the jquery-ui-sortable widget instance
+ // is still bound to the directive's element
+ var sortableWidgetInstance = getSortableWidgetInstance(element);
+ if (!!sortableWidgetInstance) {
+ var optsDiff = patchUISortableOptions(newVal, oldVal, sortableWidgetInstance);
+
+ if (optsDiff) {
+ element.sortable('option', optsDiff);
+ }
+ }
+ }, true);
+
+ patchUISortableOptions(opts);
+ }
+
+ function init () {
+ if (ngModel) {
+ wireUp();
+ } else {
+ $log.info('ui.sortable: ngModel not provided!', element);
+ }
+
+ // Create sortable
+ element.sortable(opts);
+ }
+
+ function initIfEnabled () {
+ if (scope.uiSortable && scope.uiSortable.disabled) {
+ return false;
+ }
+
+ init();
+
+ // Stop Watcher
+ initIfEnabled.cancelWatcher();
+ initIfEnabled.cancelWatcher = angular.noop;
+
+ return true;
+ }
+
+ initIfEnabled.cancelWatcher = angular.noop;
+
+ if (!initIfEnabled()) {
+ initIfEnabled.cancelWatcher = scope.$watch('uiSortable.disabled', initIfEnabled);
+ }
+ }
+ };
+ }
+ ]);