Răsfoiți Sursa

[home2] Enable sharing

With this I've improved and moved the code from share2.vm.js into hueDocument.js. For now sharing is only possible when a single document is selected.
Johan Ahlen 9 ani în urmă
părinte
comite
fb21cb3

+ 27 - 0
desktop/core/src/desktop/static/desktop/js/assist/assistHelper.js

@@ -27,6 +27,7 @@
   var HDFS_API_PREFIX = "/filebrowser/view=";
   var HDFS_PARAMETERS = "?pagesize=100&format=json";
   var DOCUMENTS_API = "/desktop/api2/docs/";
+  var DOCUMENT_API = "/desktop/api2/doc/get";
 
 
   /**
@@ -221,6 +222,32 @@
       .fail(self.assistErrorCallback(options));
   };
 
+  /**
+   * @param {Object} options
+   * @param {Function} options.successCallback
+   * @param {Function} [options.errorCallback]
+   * @param {boolean} [options.silenceErrors]
+   *
+   * @param {number} options.docId
+   */
+  AssistHelper.prototype.fetchDocument = function (options) {
+    var self = this;
+    $.ajax({
+      url: DOCUMENT_API,
+      data: {
+        id: options.docId
+      },
+      success: function (data) {
+        if (! self.successResponseIsError(data)) {
+          options.successCallback(data);
+        } else {
+          self.assistErrorCallback(options)(data);
+        }
+      }
+    })
+    .fail(self.assistErrorCallback(options));
+  };
+
   /**
    * @param {Object} options
    * @param {Function} options.successCallback

+ 241 - 0
desktop/core/src/desktop/static/desktop/js/fileBrowser/hueDocument.js

@@ -0,0 +1,241 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function (root, factory) {
+  if(typeof define === "function" && define.amd) {
+    define([
+      'knockout'
+    ], factory);
+  } else {
+    root.HueDocument = factory(ko);
+  }
+}(this, function (ko) {
+
+  /**
+   *
+   * @param {Object} options
+   * @param {AssistHelper} options.assistHelper
+   * @param {HueFileEntry} options.fileEntry
+   *
+   * @constructor
+   */
+  function HueDocument (options) {
+    var self = this;
+    self.fileEntry = options.fileEntry;
+    self.assistHelper = options.assistHelper;
+    self.definition = ko.observable();
+    self.loaded = ko.observable(false);
+    self.loading = ko.observable(false);
+    self.hasErrors = ko.observable(false);
+
+    self.selectedUserOrGroup = ko.observable();
+    self.selectedPerm = ko.observable('read');
+
+    self.availableUsersAndGroups = {};
+  };
+
+  HueDocument.prototype.initUserTypeahead = function (callback) {
+    var self = this;
+    $.getJSON('/desktop/api/users/autocomplete', function (data) {
+      self.availableUsersAndGroups = data;
+      self.prettifyUserNames(self.availableUsersAndGroups.users);
+      var dropdown = [];
+      var usermap = {};
+      var groupmap = {};
+
+      $.each(self.availableUsersAndGroups.users, function (i, user) {
+        usermap[user.prettyName] = user;
+        dropdown.push(user.prettyName);
+      });
+
+      $.each(self.availableUsersAndGroups.groups, function (i, group) {
+        groupmap[group.name] = group;
+        dropdown.push(group.name);
+      });
+
+      $("#documentShareTypeahead").typeahead({
+        source: function (query, process) {
+          process(dropdown);
+        },
+        matcher: function (item) {
+          if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
+            return true;
+          }
+        },
+        sorter: function (items) {
+          return items.sort();
+        },
+        highlighter: function (item) {
+          var _icon = "fa";
+          if (usermap[item]) {
+            _icon += " fa-user";
+          }
+          else {
+            _icon += " fa-users";
+          }
+          var regex = new RegExp('(' + this.query + ')', 'gi');
+          return "<i class='" + _icon + "'></i> " + item.replace(regex, "<strong>$1</strong>");
+        },
+        updater: function (item) {
+          self.selectedUserOrGroup(usermap[item] ? usermap[item] : groupmap[item]);
+          return item;
+        }
+      });
+
+      $("#documentShareTypeahead").on("keyup", function (e) {
+        var _code = (e.keyCode ? e.keyCode : e.which);
+        if (_code == 13) {
+          self.handleTypeAheadSelection();
+        }
+      });
+
+      if (typeof id == "function"){
+        id();
+      }
+      callback();
+    }).fail(function (response) {
+      $(document).trigger("error", "There was an error processing your action: " + response.responseText);
+    });
+  };
+
+  HueDocument.prototype.handleTypeAheadSelection = function () {
+    var self = this;
+    if (self.selectedUserOrGroup() !== null) {
+      if (typeof self.selectedUserOrGroup().username !== 'undefined') {
+        self.definition().perms[self.selectedPerm()].users.push(self.selectedUserOrGroup());
+      } else {
+        self.definition().perms[self.selectedPerm()].groups.push(self.selectedUserOrGroup());
+      }
+      self.persistPerms();
+    }
+    self.selectedUserOrGroup(null);
+    $("#documentShareTypeahead").val("");
+  };
+
+  HueDocument.prototype.persistPerms = function () {
+    var self = this;
+    var postPerms = {
+      read: {
+        user_ids: $.map(self.definition().perms.read.users, function (user) { return user.id }),
+        group_ids: $.map(self.definition().perms.read.groups, function (group) { return group.id }),
+      },
+      write: {
+        user_ids: $.map(self.definition().perms.write.users, function (user) { return user.id }),
+        group_ids: $.map(self.definition().perms.write.groups, function (group) { return group.id }),
+      }
+    };
+
+    $.post("/desktop/api2/doc/share", {
+      doc_id: self.fileEntry.definition.id,
+      data: JSON.stringify(postPerms)
+    }, function (response) {
+      if (response != null) {
+        if (response.status != 0) {
+          $(document).trigger("error", "There was an error processing your action: " + response.message);
+        } else {
+          self.load();
+        }
+      }
+    }).fail(function (response) {
+      $(document).trigger("error", "There was an error processing your action: " + response.responseText);
+    });
+  };
+
+  HueDocument.prototype.load = function () {
+    var self = this;
+    if (self.loading()) {
+      return;
+    }
+
+    self.loading(true);
+    self.hasErrors(false);
+
+    var fetchFunction = function () {
+      self.assistHelper.fetchDocument({
+        docId: self.fileEntry.definition.id,
+        successCallback: function (data) {
+          self.prettifyUserNames(data.perms.write.users);
+          self.prettifyUserNames(data.perms.read.users);
+          self.definition(data);
+          self.loading(false);
+          self.loaded(true);
+        },
+        errorCallback: function () {
+          self.hasErrors(true);
+          self.loading(false);
+          self.loaded(true);
+        }
+      })
+    };
+
+    if (!self.loaded()) {
+      window.setTimeout(function () {
+        self.initUserTypeahead(fetchFunction);
+      }, 2000);
+    } else {
+      fetchFunction();
+    }
+  };
+
+  HueDocument.prototype.prettifyUserNames = function (users) {
+    $.each(users, function (idx, user) {
+      user.prettyName = '';
+      if (user.first_name) {
+        user.prettyName += user.first_name + ' ';
+      }
+      if (user.last_name) {
+        user.prettyName += user.last_name + ' ';
+      }
+      if (user.prettyName) {
+        user.prettyName += '(' + user.username + ')';
+      } else {
+        user.prettyName += user.username;
+      }
+    });
+  };
+
+  HueDocument.prototype.removeFromPerms = function (perms, id) {
+    var self = this;
+    $(perms).each(function (cnt, item) {
+      if (item.id == id) {
+        perms.splice(cnt, 1);
+      }
+    });
+    self.persistPerms();
+  };
+
+  HueDocument.prototype.removeUserReadShare = function (user) {
+    var self = this;
+    self.removeFromPerms(self.definition().perms.read.users, user.id);
+  };
+
+  HueDocument.prototype.removeUserWriteShare = function (user) {
+    var self = this;
+    self.removeFromPerms(self.definition().perms.write.users, user.id);
+  };
+
+  HueDocument.prototype.removeGroupReadShare = function (group) {
+    var self = this;
+    self.updateSharePerm(self.definition().perms.read.groups, group.id);
+  };
+
+  HueDocument.prototype.removeGroupWriteShare = function (group) {
+    var self = this;
+    self.updateSharePerm(self.definition().perms.write.groups, group.id);
+  };
+
+  return HueDocument;
+}));

+ 25 - 2
desktop/core/src/desktop/static/desktop/js/fileBrowser/hueFileEntry.js

@@ -17,12 +17,13 @@
 (function (root, factory) {
   if(typeof define === "function" && define.amd) {
     define([
-      'knockout'
+      'knockout',
+      'desktop/js/fileBrowser/hueDocument'
     ], factory);
   } else {
     root.HueFileEntry = factory(ko);
   }
-}(this, function (ko) {
+}(this, function (ko, HueDocument) {
 
   /**
    *
@@ -47,6 +48,8 @@
     self.path = self.definition.name;
     self.app = options.app;
 
+    self.activeDocument = ko.observable();
+
     self.entriesToDelete = ko.observableArray();
 
     self.selected = ko.observable(false);
@@ -67,6 +70,13 @@
       });
     });
 
+    self.selectedEntry = ko.pureComputed(function () {
+      if (self.selectedEntries().length === 1) {
+        return self.selectedEntries()[0];
+      }
+      return null;
+    });
+
     self.breadcrumbs = [];
     var lastParent = self.parent;
     while (lastParent) {
@@ -75,6 +85,19 @@
     }
   }
 
+  HueFileEntry.prototype.showSharingModal = function () {
+    var self = this;
+    if (self.selectedEntry()) {
+      if (! self.activeDocument()) {
+        self.activeDocument(new HueDocument({
+          assistHelper: self.assistHelper,
+          fileEntry: self
+        }));
+        self.activeDocument().load();
+      }
+      $('#shareDocumentModal').modal('show');
+    }
+  };
 
   /**
    * @param {HueFileEntry[]} entries

+ 0 - 287
desktop/core/src/desktop/static/desktop/js/share2.vm.js

@@ -1,287 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-(function (root, factory) {
-  if(typeof define === "function" && define.amd) {
-    define([
-      'knockout',
-      'knockout-mapping'
-    ], factory);
-  } else {
-    root.ShareViewModel = factory(ko);
-  }
-}(this, function (ko) {
-
-  function ShareViewModel(updateDocF) {
-    var self = this;
-
-    self.hasInitBeenCalled = false;
-    self.hasSetupBeenCalled = false;
-
-    self.selectedPerm = ko.observable('read');
-    self.selectedPermLabel = ko.computed(function() {
-      if (self.selectedPerm() == 'write') {
-        return 'Modify';
-      } else {
-        return 'Read';
-      }
-    });
-
-    self.selectedDoc = ko.observable(ko.mapping.fromJS({
-      perms: {
-        read: {
-          users: [],
-          groups: []
-        },
-        write: {
-          users: [],
-          groups: []
-        }
-      }
-    }))
-
-    self.updateDoc = updateDocF
-
-    self.setDocId = function(docId) {
-      if (docId == -1) { return false; }
-      $.get('/desktop/api2/doc/get', { id : docId }, function (data) {
-        shareViewModel.selectedDoc(data);
-      }).fail(function (response) {
-        $(document).trigger("error", "There was an error processing your action: " + response.responseText);
-      });
-    }
-  }
-
-
-  function openShareModal() {
-    $("#documentShareModal").modal("show");
-    setupSharing(function(){
-      $("#documentShareAddBtn").removeClass("disabled");
-      $("#documentShareCaret").removeClass("disabled");
-      $("#documentShareTypeahead").removeAttr("disabled");
-      $(".notpretty").each(function(){
-        $(this).text(prettifyUsername($(this).attr("data-id")));
-      });
-    });
-  }
-
-  function isShared() {
-    if (!shareViewModel) { return false; }
-    read = shareViewModel.selectedDoc().perms.read;
-    return read.users.length + read.groups.length > 0
-  }
-
-  function prettifyUsername(userId) {
-    var _user = null;
-    if (self.hasSetupBeenCalled) {
-      for (var i = 0; i < JSON_USERS_GROUPS.users.length; i++) {
-        if (JSON_USERS_GROUPS.users[i].id == userId) {
-          _user = JSON_USERS_GROUPS.users[i];
-        }
-      }
-      if (_user != null) {
-        return (_user.first_name != "" ? _user.first_name + " " : "") + (_user.last_name != "" ? _user.last_name + " " : "") + ((_user.first_name != "" || _user.last_name != "") ? "(" : "") + _user.username + ((_user.first_name != "" || _user.last_name != "") ? ")" : "");
-      }
-    }
-    return "";
-  }
-
-  function initSharing(id, updateFunc) {
-    if(! updateFunc) {
-      updateFunc = function () {}
-    }
-    shareViewModel = new ShareViewModel(updateFunc);
-    ko.applyBindings(shareViewModel, $(id)[0]);
-    shareViewModel.hasInitBeenCalled = true;
-    return shareViewModel;
-  }
-
-  function setupSharing(id, updateFunc) {
-    if (shareViewModel == null || !shareViewModel.hasInitBeenCalled) {
-      shareViewModel = initSharing(id, updateFunc);
-    }
-
-    if (! self.hasSetupBeenCalled){
-      $.getJSON('/desktop/api/users/autocomplete', function (data) {
-        self.hasSetupBeenCalled = true;
-        JSON_USERS_GROUPS = data;
-        dropdown = [];
-        usermap = {};
-        groupmap = {};
-
-        $.each(JSON_USERS_GROUPS.users, function (i, user) {
-          var _display = prettifyUsername(user.id);
-          usermap[_display] = user;
-          dropdown.push(_display);
-        });
-
-        $.each(JSON_USERS_GROUPS.groups, function (i, group) {
-          groupmap[group.name] = group;
-          dropdown.push(group.name);
-        });
-
-        $("#documentShareTypeahead").typeahead({
-          source: function (query, process) {
-            process(dropdown);
-          },
-          matcher: function (item) {
-            if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
-              return true;
-            }
-          },
-          sorter: function (items) {
-            return items.sort();
-          },
-          highlighter: function (item) {
-            var _icon = "fa";
-            if (usermap[item]) {
-              _icon += " fa-user";
-            }
-            else {
-              _icon += " fa-users";
-            }
-            var regex = new RegExp('(' + this.query + ')', 'gi');
-            return "<i class='" + _icon + "'></i> " + item.replace(regex, "<strong>$1</strong>");
-          },
-          updater: function (item) {
-            selectedUserOrGroup = usermap[item] ? usermap[item] : groupmap[item];
-            return item;
-          }
-        });
-
-        $("#documentShareTypeahead").on("keyup", function (e) {
-          var _code = (e.keyCode ? e.keyCode : e.which);
-          if (_code == 13) {
-            handleTypeaheadSelection();
-          }
-        });
-
-        if (typeof id == "function"){
-          id();
-        }
-      }).fail(function (response) {
-        $(document).trigger("error", "There was an error processing your action: " + response.responseText);
-      });
-
-      $("#documentShareAddBtn").on("click", function () {
-        if (! $(this).hasClass("disabled")){
-          handleTypeaheadSelection();
-        }
-      });
-    }
-    else {
-      if (typeof id == "function"){
-        id();
-      }
-    }
-
-    return shareViewModel;
-  }
-
-  function updateSharePerm(perms, user) {
-    $(perms).each(function (cnt, item) {
-      if (item.id == user.id) {
-        perms.splice(cnt, 1);
-      }
-    });
-    shareViewModel.selectedDoc.valueHasMutated();
-    shareDocFinal();
-  }
-
-  function removeUserReadShare(user) {
-    updateSharePerm(shareViewModel.selectedDoc().perms.read.users, user);
-  }
-
-  function removeUserWriteShare(user) {
-    updateSharePerm(shareViewModel.selectedDoc().perms.write.users, user);
-  }
-
-  function removeGroupReadShare(group) {
-    updateSharePerm(shareViewModel.selectedDoc().perms.read.groups, group);
-  }
-
-  function removeGroupWriteShare(group) {
-    updateSharePerm(shareViewModel.selectedDoc().perms.write.groups, group);
-  }
-
-  function changeDocumentSharePerm(perm) {
-    shareViewModel.selectedPerm(perm);
-  }
-
-  function handleTypeaheadSelection() {
-    if (selectedUserOrGroup != null) {
-      if (selectedUserOrGroup.hasOwnProperty("username")) {
-        shareViewModel.selectedDoc().perms[shareViewModel.selectedPerm()].users.push(selectedUserOrGroup);
-      }
-      else {
-        shareViewModel.selectedDoc().perms[shareViewModel.selectedPerm()].groups.push(selectedUserOrGroup);
-      }
-      shareViewModel.selectedDoc.valueHasMutated();
-      shareDocFinal();
-    }
-    selectedUserOrGroup = null;
-    $("#documentShareTypeahead").val("");
-  }
-
-  function shareDocFinal() {
-    var _postPerms = {
-      read: {
-        user_ids: [],
-        group_ids: []
-      },
-      write: {
-        user_ids: [],
-        group_ids: []
-      }
-    }
-
-    $(shareViewModel.selectedDoc().perms.read.users).each(function (cnt, item) {
-      _postPerms.read.user_ids.push(item.id);
-    });
-
-    $(shareViewModel.selectedDoc().perms.read.groups).each(function (cnt, item) {
-      _postPerms.read.group_ids.push(item.id);
-    });
-
-    $(shareViewModel.selectedDoc().perms.write.users).each(function (cnt, item) {
-      _postPerms.write.user_ids.push(item.id);
-    });
-
-    $(shareViewModel.selectedDoc().perms.write.groups).each(function (cnt, item) {
-      _postPerms.write.group_ids.push(item.id);
-    });
-
-    $.post("/desktop/api2/doc/share", {
-      doc_id: shareViewModel.selectedDoc().id,
-      data: JSON.stringify(_postPerms)
-    }, function (response) {
-      if (response != null) {
-        if (response.status != 0) {
-          $(document).trigger("error", "There was an error processing your action: " + response.message);
-        }
-        else {
-          shareViewModel.updateDoc(response.doc);
-        }
-      }
-    }).fail(function (response) {
-      $(document).trigger("error", "There was an error processing your action: " + response.responseText);
-    });
-  }
-
-  return {
-    initSharing: initSharing
-  }
-}));

+ 79 - 2
desktop/core/src/desktop/templates/file_browser.mako

@@ -23,7 +23,6 @@ from desktop.views import _ko
 
 <%def name="fileBrowser()">
   <style>
-
     .fb-container {
       position: absolute;
       top: 0;
@@ -239,6 +238,79 @@ from desktop.views import _ko
     <div class="fb-drag-helper">
       <i class="fa fa-fw"></i><span class="drag-text">4 entries</span>
     </div>
+
+    <div id="shareDocumentModal" class="modal hide fade">
+      <!-- ko with: activeEntry -->
+      <!-- ko with: activeDocument -->
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+        <h3>${_('Sharing Settings')}</h3>
+      </div>
+      <div class="modal-body" style="overflow-y: visible">
+        <!-- ko with: definition -->
+        <div class="row-fluid" data-bind="visible: !$parent.hasErrors()">
+          <div class="span6">
+            <h4 class="muted" style="margin-top:0px">${_('Read')}</h4>
+            <div data-bind="visible: (perms.read.users.length == 0 && perms.read.groups.length == 0)">${_('The document is not shared for read.')}</div>
+            <ul class="unstyled airy" data-bind="foreach: perms.read.users">
+              <li>
+                <span class="badge badge-info badge-left"><i class="fa fa-user"></i> <span data-bind="text: prettyName, css:{ 'notpretty': prettyName === '' }, attr:{ 'data-id': id }"></span></span>
+                <span class="badge badge-right trash-share" data-bind="click: function() { $parents[1].removeUserReadShare($data) }"> <i class="fa fa-times"></i></span>
+              </li>
+            </ul>
+            <ul class="unstyled airy" data-bind="foreach: perms.read.groups">
+              <li>
+                <span class="badge badge-info badge-left"><i class="fa fa-users"></i> ${ _('Group') } &quot;<span data-bind="text: name"></span>&quot;</span>
+                <span class="badge badge-right trash-share" data-bind="click: function() { $parents[1].removeGroupReadShare($data) }"> <i class="fa fa-times"></i></span>
+              </li>
+            </ul>
+          </div>
+
+          <div class="span6">
+            <h4 class="muted" style="margin-top:0px">${_('Modify')}</h4>
+            <div data-bind="visible: (perms.write.users.length == 0 && perms.write.groups.length == 0)">${_('The document is not shared for modify.')}</div>
+            <ul class="unstyled airy" data-bind="foreach: perms.write.users">
+              <li>
+                <span class="badge badge-info badge-left"><i class="fa fa-user"></i> <span data-bind="text: prettyName, css:{'notpretty': prettyName == ''}, attr:{'data-id': id}"></span></span>
+                <span class="badge badge-right trash-share" data-bind="click: function() { $parents[1].removeUserWriteShare($data) }"> <i class="fa fa-times"></i></span>
+              </li>
+            </ul>
+            <ul class="unstyled airy" data-bind="foreach: perms.write.groups">
+              <li>
+                <span class="badge badge-info badge-left"><i class="fa fa-users"></i> ${ _('Group') } &quot;<span data-bind="text: name"></span>&quot;</span>
+                <span class="badge badge-right trash-share" data-bind="click: function() { $parents[1].removeGroupWriteShare($data) }"> <i class="fa fa-times"></i></span>
+              </li>
+            </ul>
+          </div>
+        </div>
+        <!-- /ko -->
+        <div class="fb-empty animated" style="display: none;" data-bind="visible: loading">
+          <i class="fa fa-spinner fa-spin fa-2x" style="color: #999;"></i>
+        </div>
+        <div class="fb-empty animated" style="display: none;" data-bind="visible: hasErrors() && ! loading()">
+          ${ _('There was an error loading the document.')}
+        </div>
+        <div style="margin-top: 20px" data-bind="visible: ! hasErrors() && ! loading()">
+          <div class="input-append">
+            <input id="documentShareTypeahead" type="text" style="width: 420px" placeholder="${_('Type a username or a group name')}">
+            <div class="btn-group" style="overflow:visible">
+              <a class="btn" data-bind="click: handleTypeAheadSelection"><i class="fa fa-plus-circle"></i> <span data-bind="text: selectedPerm() == 'read' ? '${ _('Read') }' : '${ _('Modify') }'"></span></a>
+              <a class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></a>
+              <ul class="dropdown-menu">
+                <li><a data-bind="click: function () { selectedPerm('read') }" href="javascript:void(0)">${ _('Read') }</a></li>
+                <li><a data-bind="click: function () { selectedPerm('write') }" href="javascript:void(0)">${ _('Modify') }</a></li>
+              </ul>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <a href="#" data-dismiss="modal" class="btn btn-primary disable-feedback disable-enter">${_('Close')}</a>
+      </div>
+      <!-- /ko -->
+      <!-- /ko -->
+    </div>
+
     <div id="importDocumentsModal" class="modal hide fade fileupload-modal">
       <!-- ko with: activeEntry -->
       <div class="modal-header">
@@ -272,6 +344,7 @@ from desktop.views import _ko
 
       <!-- /ko -->
     </div>
+
     <div id="createDirectoryModal" class="modal hide fade">
       <!-- ko with: activeEntry -->
       <div class="modal-body form-horizontal">
@@ -288,6 +361,7 @@ from desktop.views import _ko
       </div>
       <!-- /ko -->
     </div>
+
     <div id="deleteEntriesModal" class="modal hide fade">
       <!-- ko with: activeEntry -->
       <div class="modal-header">
@@ -303,6 +377,7 @@ from desktop.views import _ko
       </div>
       <!-- /ko -->
     </div>
+
     <div class="fb-container">
       <div class="fb-action-bar">
         <h4>
@@ -355,7 +430,9 @@ from desktop.views import _ko
           <!-- ko if: isRoot && selectedEntries().length == 0 -->
           <span class="inactive-action fb-action"><i class="fa fa-fw fa-times"></i></span>
           <!-- /ko -->
-          <a class="inactive-action fb-action" href="javascript:void(0);"><i class="fa fa-fw fa-users"></i></a>
+          <!-- ko if: app === 'documents' -->
+          <a class="inactive-action fb-action" href="javascript:void(0);" data-bind="click: showSharingModal"><i class="fa fa-fw fa-users"></i></a>
+          <!-- /ko -->
           <a class="inactive-action fb-action" href="javascript:void(0);" data-bind="click: download"><i class="fa fa-fw fa-download"></i></a>
           <a class="inactive-action fb-action" href="javascript:void(0);" data-bind="click: showUploadModal"><i class="fa fa-fw fa-upload"></i></a>
         </div>