You have incoming share requests. If you want to the credential in a other vault, logout of this vault and login to the vault you want the shared credential in. {{active_vault.vault_id}}
You have incoming share requests. If you want to the credential in a other vault, logout of this vault and login to the vault you want the shared credential in. {{active_vault.vault_id}}
');
}]);
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);
+ }
+ }
+ };
+ }
+ ]);
diff --git a/sass/app.scss b/sass/app.scss
index ea7fecd2..85e7049a 100644
--- a/sass/app.scss
+++ b/sass/app.scss
@@ -95,4 +95,8 @@
display: inline-block;
height: 36px;
padding: 7px 10px;
+}
+.nopadding{
+ padding-right: 0;
+ padding-left: 0;
}
\ No newline at end of file
diff --git a/sass/credentials.scss b/sass/credentials.scss
index b45a02dc..7309090d 100644
--- a/sass/credentials.scss
+++ b/sass/credentials.scss
@@ -296,11 +296,32 @@
}
}
}
+ .field-value{
+ .valueInput{
+ padding-right: 0;
+ input{
+ @include border-right-radius(0);
+ }
+ .pw-gen .generate_pw .cell:last-child{
+ @include border-right-radius(0);
+ }
+ }
+ .selectType{
+ padding-left: 0;
+ margin-left: -4px;
+ select{
+ @include border-left-radius(0);
+ }
+ }
+ }
.custom_fields, .files {
margin-top: 10px;
table {
width: 100%;
thead {
+ th.dragger{
+ width: 3%;
+ }
th {
color: #fff;
}
@@ -312,6 +333,14 @@
background-color: transparent;
}
tr {
+ td.dragger{
+ width: 3%;
+ text-align: center;
+ cursor:move;
+ cursor:-webkit-grab;
+ cursor:-moz-grab;
+ cursor:grab;
+ }
td.field_actions {
font-size: 13px;
width: 15%;
@@ -454,4 +483,28 @@
100% {
transform: rotate(360deg);
}
+}
+
+.inputfile{
+ width: 0.1px;
+ height: 0.1px;
+ opacity: 0;
+ overflow: hidden;
+ position: absolute;
+ z-index: -1;
+}
+.inputfile + label {
+ font-size: 1.25em;
+ background-color: rgba(240,240,240,.9);
+ margin-top: 4px;
+ padding: 5px;
+ margin-right: 4px;
+ border-right: 1px solid #c9c9c9;
+}
+.inputfile:focus + label,
+.inputfile + label:hover {
+ background-color: #c9c9c9;
+}
+.inputfile + label {
+ cursor: pointer; /* "hand" cursor */
}
\ No newline at end of file
diff --git a/templates/bookmarklet.php b/templates/bookmarklet.php
index 6bae81b8..fe30b180 100644
--- a/templates/bookmarklet.php
+++ b/templates/bookmarklet.php
@@ -14,6 +14,7 @@ script('passman', 'vendor/angular-off-click/angular-off-click.min');
script('passman', 'vendor/angularjs-datetime-picker/angularjs-datetime-picker.min');
script('passman', 'vendor/ng-password-meter/ng-password-meter');
script('passman', 'vendor/sjcl/sjcl');
+script('passman', 'vendor/ui-sortable/sortable');
script('passman', 'vendor/zxcvbn/zxcvbn');
script('passman', 'vendor/ng-clipboard/clipboard.min');
script('passman', 'vendor/ng-clipboard/ngclipboard');
diff --git a/templates/main.php b/templates/main.php
index e3c963d4..ef35608a 100644
--- a/templates/main.php
+++ b/templates/main.php
@@ -23,6 +23,7 @@ script('passman', 'vendor/sha/sha');
script('passman', 'vendor/llqrcode/llqrcode');
script('passman', 'vendor/forge.0.6.9.min');
script('passman', 'vendor/download');
+script('passman', 'vendor/ui-sortable/sortable');
script('passman', 'lib/promise');
script('passman', 'lib/crypto_wrap');
diff --git a/templates/views/partials/forms/edit_credential/custom_fields.html b/templates/views/partials/forms/edit_credential/custom_fields.html
index e7590496..ee245ba0 100644
--- a/templates/views/partials/forms/edit_credential/custom_fields.html
+++ b/templates/views/partials/forms/edit_credential/custom_fields.html
@@ -1,17 +1,41 @@
\ No newline at end of file
diff --git a/templates/views/partials/forms/edit_credential/files.html b/templates/views/partials/forms/edit_credential/files.html
index ed675005..01295481 100644
--- a/templates/views/partials/forms/edit_credential/files.html
+++ b/templates/views/partials/forms/edit_credential/files.html
@@ -1,6 +1,7 @@