diff options
author | brantje <brantje@gmail.com> | 2016-10-24 11:53:59 +0300 |
---|---|---|
committer | brantje <brantje@gmail.com> | 2016-10-24 20:56:04 +0300 |
commit | 8fab6a8725f49834bdb93eb96679f9b6cca0224f (patch) | |
tree | f6e470f4bbd4f7a98c8862a0d2bd6e1e1a14f8db /js | |
parent | 74f4486b6367eb431e9af1bf539f639a57c9db31 (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.js | 9 | ||||
-rw-r--r-- | js/app/app_public.js | 1 | ||||
-rw-r--r-- | js/app/controllers/bookmarklet.js | 48 | ||||
-rw-r--r-- | js/app/controllers/edit_credential.js | 65 | ||||
-rw-r--r-- | js/templates.js | 6 | ||||
-rw-r--r-- | js/vendor/download.js | 6 | ||||
-rw-r--r-- | js/vendor/ui-sortable/sortable.js | 504 |
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); + } + } + }; + } + ]); |