diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index cfaacb498d..e75b5091fb 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -231,7 +231,7 @@ block content .row.row-spaced .col-md-12 .card.card-thin - ul.list-unstyled.project-list( + ul.list-unstyled.project-list.structured-list( select-all-list, ng-if="projects.length > 0", ng-cloak @@ -243,11 +243,11 @@ block content select-all, type="checkbox" ) - span.title TITLE + | TITLE .col-md-2 - span.owner OWNER + | OWNER .col-md-4 - span.last-modified LAST MODIFIED + | LAST MODIFIED li.project_entry.container-fluid( ng-repeat="project in visibleProjects | orderBy:'lastUpdated':true", ng-controller="ProjectListItemController" diff --git a/services/web/app/views/subscriptions/group_admin.jade b/services/web/app/views/subscriptions/group_admin.jade index 823b57beeb..fba2978e83 100644 --- a/services/web/app/views/subscriptions/group_admin.jade +++ b/services/web/app/views/subscriptions/group_admin.jade @@ -1,52 +1,84 @@ extends ../layout block content - .container.box - .row - .span12 - .page-header - h2 Group Admin + .content.content-alt + .container + .row + .col-md-10.col-md-offset-1 + .card(ng-controller="GroupMembersController") + .page-header + .pull-right(ng-cloak) + small(ng-show="selectedUsers.length == 0") You have added {{ users.length }} of {{ groupSize }} available members + a.btn.btn-danger( + href, + ng-show="selectedUsers.length > 0" + ng-click="removeMembers()" + ) Remove from group + h1 Group Account - div You are allowed up to - strong #{subscription.membersLimit} - | members in this group + .row-spaced-small + ul.list-unstyled.structured-list( + select-all-list, + ng-cloak + ) + li.container-fluid + .row + .col-md-5 + input.select-all( + select-all, + type="checkbox" + ) + span.email EMAIL + .col-md-5 + span.name NAME + .col-md-2 + span.registered REGISTERED + li.container-fluid( + ng-repeat="user in users | orderBy:'email':true", + ng-controller="GroupMemberListItemController" + ) + .row + .col-md-5 + input.select-item( + select-individual, + type="checkbox", + ng-model="user.selected" + ) + span.email {{ user.email }} + .col-md-5 + span.name {{ user.first_name }} {{ user.last_name }} + .col-md-2 + span.registered + i.fa.fa-check.text-success(ng-show="!user.holdingAccount") + i.fa.fa-times(ng-show="user.holdingAccount") + li( + ng-if="users.length == 0", + ng-cloak + ) + .row + .col-md-12.text-centered + small No members - table.table-striped.table.table-striped - thead - tr - th - input(type="checkbox").select-all - th email - th Name - th Registered + div(ng-if="users.length < groupSize", ng-cloak) + hr + p + .small Add more members + form.form + .row + .col-xs-6 + input.form-control( + name="email", + type="text", + placeholder="jane@example.com, joe@example.com", + ng-model="inputs.emails", + on-enter="addMembers()" + ) + .col-xs-6 + button.btn.btn-primary(ng-click="addMembers()") Add - tbody#userList - -each user in users - tr - td - input(type="checkbox").select-one - td #{user.email} - td #{user.first_name} #{user.last_name} - td #{!user.holdingAccount} - td - input(type="hidden", name="user_id", value=user._id).user_id {{user._id}} - - - div - button.btn.btn-danger#deleteUsers Delete Selected - div   - div - form.well.form-inline#addUserToGroup - div - input(name="_csrf", type="hidden", value=csrfToken) - input(name="email", type="email", placeholder="someone@email.com")#newEmail.email.input-large   - button.btn.btn-primary.addUser Add - div   - div Add multiple emails seperated with commas or space. - - - - locals.supressDefaultJs = true - script(data-main='/js/SubscriptionGroupsManager.js', src='/js/libs/require.js') + script(type="text/javascript"). + window.users = !{JSON.stringify(users)}; + window.groupSize = #{subscription.membersLimit}; diff --git a/services/web/public/coffee/app/directives/selectAll.coffee b/services/web/public/coffee/app/directives/selectAll.coffee new file mode 100644 index 0000000000..79b683c20a --- /dev/null +++ b/services/web/public/coffee/app/directives/selectAll.coffee @@ -0,0 +1,67 @@ +define [ + "base" +], (App) -> + App.directive "selectAllList", () -> + return { + controller: ["$scope", ($scope) -> + # Selecting or deselecting all should apply to all projects + selectAll = () -> + $scope.$broadcast "select-all:select" + + deselectAll = () -> + $scope.$broadcast "select-all:deselect" + + clearSelectAllState = () -> + $scope.$broadcast "select-all:clear" + + return { + clearSelectAllState: clearSelectAllState + selectAll: selectAll + deselectAll: deselectAll + } + ] + link: (scope, element, attrs) -> + + + } + + App.directive "selectAll", () -> + return { + require: "^selectAllList" + link: (scope, element, attrs, selectAllListController) -> + scope.$on "select-all:clear", () -> + element.prop("checked", false) + + element.change () -> + if element.is(":checked") + selectAllListController.selectAll() + else + selectAllListController.deselectAll() + return true + } + + App.directive "selectIndividual", () -> + return { + require: "^selectAllList" + scope: { + ngModel: "=" + } + link: (scope, element, attrs, selectAllListController) -> + ignoreChanges = false + + scope.$watch "ngModel", (value) -> + if value? and !ignoreChanges + selectAllListController.clearSelectAllState() + + scope.$on "select-all:select", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = true + ignoreChanges = false + + scope.$on "select-all:deselect", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = false + ignoreChanges = false + } \ No newline at end of file diff --git a/services/web/public/coffee/app/main.coffee b/services/web/public/coffee/app/main.coffee index 452c661b8f..c2a70fdb78 100644 --- a/services/web/public/coffee/app/main.coffee +++ b/services/web/public/coffee/app/main.coffee @@ -3,12 +3,14 @@ define [ "main/user-details" "main/account-settings" "main/plans" + "main/group-members" "directives/asyncForm" "directives/stopPropagation" "directives/focus" "directives/equals" "directives/fineUpload" "directives/onEnter" + "directives/selectAll" "filters/formatDate" ], () -> angular.bootstrap(document.body, ["SharelatexApp"]) \ No newline at end of file diff --git a/services/web/public/coffee/app/main/group-members.coffee b/services/web/public/coffee/app/main/group-members.coffee new file mode 100644 index 0000000000..f1f61623ee --- /dev/null +++ b/services/web/public/coffee/app/main/group-members.coffee @@ -0,0 +1,54 @@ +define [ + "base" +], (App) -> + App.controller "GroupMembersController", ($scope, queuedHttp) -> + $scope.users = window.users + $scope.groupSize = window.groupSize + $scope.selectedUsers = [] + + $scope.inputs = + emails: "" + + parseEmails = (emailsString)-> + regexBySpaceOrComma = /[\s,]+/ + emails = emailsString.split(regexBySpaceOrComma) + emails = _.map emails, (email)-> + email = email.trim() + emails = _.select emails, (email)-> + email.indexOf("@") != -1 + return emails + + $scope.addMembers = () -> + emails = parseEmails($scope.inputs.emails) + for email in emails + queuedHttp + .post("/subscription/group/user", { + email: email, + _csrf: window.csrfToken + }) + .success (data) -> + $scope.users.push data.user if data.user? + $scope.inputs.emails = "" + + $scope.removeMembers = () -> + for user in $scope.selectedUsers + do (user) -> + queuedHttp({ + method: "DELETE", + url: "/subscription/group/user/#{user._id}" + headers: + "X-Csrf-Token": window.csrfToken + }) + .success () -> + index = $scope.users.indexOf(user) + return if index == -1 + $scope.users.splice(index, 1) + $scope.selectedUsers = [] + + $scope.updateSelectedUsers = () -> + $scope.selectedUsers = $scope.users.filter (user) -> user.selected + + App.controller "GroupMemberListItemController", ($scope) -> + $scope.$watch "user.selected", (value) -> + if value? + $scope.updateSelectedUsers() \ No newline at end of file diff --git a/services/web/public/coffee/app/main/project-list.coffee b/services/web/public/coffee/app/main/project-list.coffee index dc4a76c75c..0ad0c94660 100644 --- a/services/web/public/coffee/app/main/project-list.coffee +++ b/services/web/public/coffee/app/main/project-list.coffee @@ -1,71 +1,6 @@ define [ "base" ], (App) -> - App.directive "selectAllList", () -> - return { - controller: ["$scope", ($scope) -> - # Selecting or deselecting all should apply to all projects - selectAll = () -> - $scope.$broadcast "select-all:select" - - deselectAll = () -> - $scope.$broadcast "select-all:deselect" - - clearSelectAllState = () -> - $scope.$broadcast "select-all:clear" - - return { - clearSelectAllState: clearSelectAllState - selectAll: selectAll - deselectAll: deselectAll - } - ] - link: (scope, element, attrs) -> - - - } - - App.directive "selectAll", () -> - return { - require: "^selectAllList" - link: (scope, element, attrs, selectAllListController) -> - scope.$on "select-all:clear", () -> - element.prop("checked", false) - - element.change () -> - if element.is(":checked") - selectAllListController.selectAll() - else - selectAllListController.deselectAll() - return true - } - - App.directive "selectIndividual", () -> - return { - require: "^selectAllList" - scope: { - ngModel: "=" - } - link: (scope, element, attrs, selectAllListController) -> - ignoreChanges = false - - scope.$watch "ngModel", (value) -> - if value? and !ignoreChanges - selectAllListController.clearSelectAllState() - - scope.$on "select-all:select", () -> - ignoreChanges = true - scope.$apply () -> - scope.ngModel = true - ignoreChanges = false - - scope.$on "select-all:deselect", () -> - ignoreChanges = true - scope.$apply () -> - scope.ngModel = false - ignoreChanges = false - } - App.factory "queuedHttp", ["$http", "$q", ($http, $q) -> pendingRequests = [] inflight = false diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 626b4ac76a..6e1ebe3aed 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -68,16 +68,16 @@ form.project-search { } } -ul.project-list { +ul.structured-list { list-style-type: none; margin: 0; overflow: hidden; - overflow-y: scroll; + overflow-y: auto; li { border-bottom: 1px solid @gray-lightest; padding: (@line-height-computed / 4) 0; &:first-child { - .last-modified, .owner { + .header { font-size: 1rem; } } @@ -101,6 +101,11 @@ ul.project-list { .select-item, .select-all { margin-left: @line-height-computed / 4; } + } +} + +ul.project-list { + li { .last-modified, .owner { font-size: .8rem; }