mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into sk-passport
# Conflicts: # app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee
This commit is contained in:
commit
72ca1d6316
14 changed files with 122 additions and 39 deletions
|
@ -112,14 +112,16 @@ module.exports = CollaboratorsInviteController =
|
|||
|
||||
acceptInvite: (req, res, next) ->
|
||||
projectId = req.params.Project_id
|
||||
inviteId = req.params.invite_id
|
||||
{token} = req.body
|
||||
token = req.params.token
|
||||
currentUser = AuthenticationController.getSessionUser(req)
|
||||
logger.log {projectId, inviteId, userId: currentUser._id}, "accepting invite"
|
||||
CollaboratorsInviteHandler.acceptInvite projectId, inviteId, token, currentUser, (err) ->
|
||||
logger.log {projectId, userId: currentUser._id, token}, "got request to accept invite"
|
||||
CollaboratorsInviteHandler.acceptInvite projectId, token, currentUser, (err) ->
|
||||
if err?
|
||||
logger.err {projectId, inviteId}, "error accepting invite by token"
|
||||
logger.err {projectId, token}, "error accepting invite by token"
|
||||
return next(err)
|
||||
EditorRealTimeController.emitToRoom projectId, 'project:membership:changed', {invites: true, members: true}
|
||||
AnalyticsManger.recordEvent(currentUser._id, "project-invite-accept", {inviteId:inviteId, projectId:projectId})
|
||||
res.redirect "/project/#{projectId}"
|
||||
AnalyticsManger.recordEvent(currentUser._id, "project-invite-accept", {projectId:projectId, userId:currentUser._id})
|
||||
if req.xhr
|
||||
res.sendStatus 204 # Done async via project page notification
|
||||
else
|
||||
res.redirect "/project/#{projectId}"
|
||||
|
|
|
@ -77,10 +77,12 @@ module.exports = CollaboratorsInviteHandler =
|
|||
if err?
|
||||
logger.err {err, projectId, sendingUserId: sendingUser._id, email}, "error saving token"
|
||||
return callback(err)
|
||||
# Send email and notification in background
|
||||
CollaboratorsInviteHandler._sendMessages projectId, sendingUser, invite, (err) ->
|
||||
if err?
|
||||
logger.err {projectId, email}, "error sending messages for invite"
|
||||
callback(err, invite)
|
||||
callback(null, invite)
|
||||
|
||||
|
||||
revokeInvite: (projectId, inviteId, callback=(err)->) ->
|
||||
logger.log {projectId, inviteId}, "removing invite"
|
||||
|
@ -117,15 +119,15 @@ module.exports = CollaboratorsInviteHandler =
|
|||
return callback(null, null)
|
||||
callback(null, invite)
|
||||
|
||||
acceptInvite: (projectId, inviteId, tokenString, user, callback=(err)->) ->
|
||||
logger.log {projectId, inviteId, userId: user._id}, "accepting invite"
|
||||
acceptInvite: (projectId, tokenString, user, callback=(err)->) ->
|
||||
logger.log {projectId, userId: user._id, tokenString}, "accepting invite"
|
||||
CollaboratorsInviteHandler.getInviteByToken projectId, tokenString, (err, invite) ->
|
||||
if err?
|
||||
logger.err {err, projectId, inviteId}, "error finding invite"
|
||||
logger.err {err, projectId, tokenString}, "error finding invite"
|
||||
return callback(err)
|
||||
if !invite
|
||||
err = new Errors.NotFoundError("no matching invite found")
|
||||
logger.log {err, projectId, inviteId, tokenString}, "no matching invite found"
|
||||
logger.log {err, projectId, tokenString}, "no matching invite found"
|
||||
return callback(err)
|
||||
inviteId = invite._id
|
||||
CollaboratorsHandler.addUserIdToProject projectId, invite.sendingUserId, user._id, invite.privileges, (err) ->
|
||||
|
|
|
@ -66,7 +66,7 @@ module.exports =
|
|||
)
|
||||
|
||||
webRouter.post(
|
||||
'/project/:Project_id/invite/:invite_id/accept',
|
||||
'/project/:Project_id/invite/token/:token/accept',
|
||||
AuthenticationController.requireLogin(),
|
||||
CollaboratorsInviteController.acceptInvite
|
||||
)
|
||||
|
|
|
@ -315,8 +315,12 @@ script(type='text/ng-template', id='newDocModalTemplate')
|
|||
required,
|
||||
ng-model="inputs.name",
|
||||
on-enter="create()",
|
||||
select-name-on="open"
|
||||
select-name-on="open",
|
||||
ng-pattern="validFileRegex",
|
||||
name="name"
|
||||
)
|
||||
.text-danger.row-spaced-small(ng-show="newDocForm.name.$error.pattern")
|
||||
| #{translate('files_cannot_include_invalid_characters')}
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-disabled="state.inflight"
|
||||
|
@ -341,8 +345,12 @@ script(type='text/ng-template', id='newFolderModalTemplate')
|
|||
required,
|
||||
ng-model="inputs.name",
|
||||
on-enter="create()",
|
||||
select-name-on="open"
|
||||
select-name-on="open",
|
||||
ng-pattern="validFileRegex",
|
||||
name="name"
|
||||
)
|
||||
.text-danger.row-spaced-small(ng-show="newFolderForm.name.$error.pattern")
|
||||
| #{translate('files_cannot_include_invalid_characters')}
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-disabled="state.inflight"
|
||||
|
@ -414,3 +422,13 @@ script(type='text/ng-template', id='deleteEntityModalTemplate')
|
|||
)
|
||||
span(ng-hide="state.inflight") #{translate("delete")}
|
||||
span(ng-show="state.inflight") #{translate("deleting")}...
|
||||
|
||||
script(type='text/ng-template', id='invalidFileNameModalTemplate')
|
||||
.modal-header
|
||||
h3 #{translate('invalid_file_name')}
|
||||
.modal-body
|
||||
p #{translate('files_cannot_include_invalid_characters')}
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="$close()"
|
||||
) #{translate('ok')}
|
|
@ -20,7 +20,7 @@ block content
|
|||
form.form(
|
||||
name="acceptForm",
|
||||
method="POST",
|
||||
action="/project/#{invite.projectId}/invite/#{invite._id}/accept"
|
||||
action="/project/#{invite.projectId}/invite/token/#{invite.token}/accept"
|
||||
)
|
||||
input(name='_csrf', type='hidden', value=csrfToken)
|
||||
input(name='token', type='hidden', value="#{invite.token}")
|
||||
|
|
|
@ -4,14 +4,31 @@ span(ng-controller="NotificationsController").userNotifications
|
|||
ng-cloak
|
||||
)
|
||||
li.notification_entry(
|
||||
ng-repeat="unreadNotification in notifications",
|
||||
ng-repeat="notification in notifications",
|
||||
)
|
||||
.row(ng-hide="unreadNotification.hide")
|
||||
.row(ng-hide="notification.hide")
|
||||
.col-xs-12
|
||||
.alert.alert-info
|
||||
.alert.alert-info(ng-if="notification.templateKey == 'notification_project_invite'", ng-controller="ProjectInviteNotificationController")
|
||||
div.notification_inner
|
||||
span(ng-bind-html="unreadNotification.html").notification_body
|
||||
.notification_body(ng-show="!notification.accepted")
|
||||
| !{translate("notification_project_invite_message")}
|
||||
a.pull-right.btn.btn-sm.btn-info(href, ng-click="accept()", ng-disabled="notification.inflight")
|
||||
span(ng-show="!notification.inflight") #{translate("join_project")}
|
||||
span(ng-show="notification.inflight")
|
||||
i.fa.fa-fw.fa-spinner.fa-spin
|
||||
|
|
||||
| #{translate("joining")}...
|
||||
.notification_body(ng-show="notification.accepted")
|
||||
| !{translate("notification_project_invite_accepted_message")}
|
||||
a.pull-right.btn.btn-sm.btn-info(href="/project/{{ notification.messageOpts.projectId }}") #{translate("open_project")}
|
||||
span().notification_close
|
||||
button(ng-click="dismiss(unreadNotification)").close.pull-right
|
||||
button(ng-click="dismiss(notification)").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
||||
.alert.alert-info(ng-if="notification.templateKey != 'notification_project_invite'")
|
||||
div.notification_inner
|
||||
span(ng-bind-html="notification.html").notification_body
|
||||
span().notification_close
|
||||
button(ng-click="dismiss(notification)").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
||||
|
|
|
@ -87,6 +87,8 @@ define [
|
|||
# End of tracking code.
|
||||
|
||||
window._ide = ide
|
||||
|
||||
ide.validFileRegex = '^[^\*\/]*$' # Don't allow * and /
|
||||
|
||||
ide.project_id = $scope.project_id = window.project_id
|
||||
ide.$scope = $scope
|
||||
|
|
|
@ -44,6 +44,8 @@ define [
|
|||
App.controller "NewDocModalController", [
|
||||
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
|
||||
($scope, ide, $modalInstance, $timeout, parent_folder) ->
|
||||
$scope.validFileRegex = ide.validFileRegex
|
||||
|
||||
$scope.inputs =
|
||||
name: "name.tex"
|
||||
$scope.state =
|
||||
|
@ -74,6 +76,8 @@ define [
|
|||
App.controller "NewFolderModalController", [
|
||||
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
|
||||
($scope, ide, $modalInstance, $timeout, parent_folder) ->
|
||||
$scope.validFileRegex = ide.validFileRegex
|
||||
|
||||
$scope.inputs =
|
||||
name: "name"
|
||||
$scope.state =
|
||||
|
|
|
@ -26,9 +26,23 @@ define [
|
|||
$scope.startRenaming = () ->
|
||||
$scope.entity.renaming = true
|
||||
|
||||
invalidModalShowing = false
|
||||
$scope.finishRenaming = () ->
|
||||
delete $scope.entity.renaming
|
||||
name = $scope.inputs.name
|
||||
|
||||
if !name.match(new RegExp(ide.validFileRegex))
|
||||
# Showing the modal blurs the rename box which calls us again
|
||||
# so track this with the invalidModalShowing flag
|
||||
return if invalidModalShowing
|
||||
invalidModalShowing = true
|
||||
modal = $modal.open(
|
||||
templateUrl: "invalidFileNameModalTemplate"
|
||||
)
|
||||
modal.result.then () ->
|
||||
invalidModalShowing = false
|
||||
return
|
||||
|
||||
delete $scope.entity.renaming
|
||||
if !name? or name.length == 0
|
||||
$scope.inputs.name = $scope.entity.name
|
||||
return
|
||||
|
|
|
@ -12,7 +12,9 @@ define [
|
|||
INDICATOR_DELAY2: 250 # time until the indicator starts animating
|
||||
|
||||
constructor: (@url, @options) ->
|
||||
# PDFJS.disableFontFace = true # avoids repaints, uses worker more
|
||||
if navigator.userAgent?.indexOf("Edge/") >= 0
|
||||
# Microsoft Edge does not work well with font-face (Sept 2016)
|
||||
PDFJS.disableFontFace = true
|
||||
if @options.disableAutoFetch
|
||||
PDFJS.disableAutoFetch = true # prevent loading whole file
|
||||
# PDFJS.disableStream
|
||||
|
|
|
@ -79,19 +79,20 @@ define [
|
|||
return
|
||||
|
||||
member = members.shift()
|
||||
if !member.type? and member.display in currentMemberEmails
|
||||
if member.type == "user"
|
||||
email = member.email
|
||||
else # Not an auto-complete object, so email == display
|
||||
email = member.display
|
||||
email = email.toLowerCase()
|
||||
|
||||
if email in currentMemberEmails
|
||||
# Skip this existing member
|
||||
return addNextMember()
|
||||
|
||||
# NOTE: groups aren't really a thing in ShareLaTeX, partially inherited from DJ
|
||||
if member.display in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == member.display)?._id
|
||||
if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id
|
||||
request = projectInvites.resendInvite(inviteId)
|
||||
else if member.type == "user"
|
||||
request = projectInvites.sendInvite(member.email, $scope.inputs.privileges)
|
||||
else if member.type == "group"
|
||||
request = projectMembers.addGroup(member.id, $scope.inputs.privileges)
|
||||
else # Not an auto-complete object, so email == display
|
||||
request = projectInvites.sendInvite(member.display, $scope.inputs.privileges)
|
||||
else
|
||||
request = projectInvites.sendInvite(email, $scope.inputs.privileges)
|
||||
|
||||
request
|
||||
.success (data) ->
|
||||
|
|
|
@ -15,3 +15,24 @@ define [
|
|||
})
|
||||
.success (data) ->
|
||||
notification.hide = true
|
||||
|
||||
App.controller "ProjectInviteNotificationController", ($scope, $http) ->
|
||||
# Shortcuts for translation keys
|
||||
$scope.projectName = $scope.notification.messageOpts.projectName
|
||||
$scope.userName = $scope.notification.messageOpts.userName
|
||||
|
||||
$scope.accept = () ->
|
||||
$scope.notification.inflight = true
|
||||
$http({
|
||||
url: "/project/#{$scope.notification.messageOpts.projectId}/invite/token/#{$scope.notification.messageOpts.token}/accept"
|
||||
method: "POST"
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
})
|
||||
.success () ->
|
||||
$scope.notification.inflight = false
|
||||
$scope.notification.accepted = true
|
||||
.error () ->
|
||||
$scope.notification.inflight = false
|
||||
$scope.notification.error = true
|
|
@ -536,14 +536,12 @@ describe "CollaboratorsInviteController", ->
|
|||
beforeEach ->
|
||||
@req.params =
|
||||
Project_id: @project_id
|
||||
invite_id: @invite_id = "thuseoautoh"
|
||||
token: @token = "mock-token"
|
||||
@req.session =
|
||||
user: _id: @current_user_id = "current-user-id"
|
||||
@req.body =
|
||||
token: "thsueothaueotauahsuet"
|
||||
@res.render = sinon.stub()
|
||||
@res.redirect = sinon.stub()
|
||||
@CollaboratorsInviteHandler.acceptInvite = sinon.stub().callsArgWith(4, null)
|
||||
@CollaboratorsInviteHandler.acceptInvite = sinon.stub().callsArgWith(3, null)
|
||||
@callback = sinon.stub()
|
||||
@next = sinon.stub()
|
||||
|
||||
|
@ -557,7 +555,9 @@ describe "CollaboratorsInviteController", ->
|
|||
@res.redirect.calledWith("/project/#{@project_id}").should.equal true
|
||||
|
||||
it 'should have called acceptInvite', ->
|
||||
@CollaboratorsInviteHandler.acceptInvite.callCount.should.equal 1
|
||||
@CollaboratorsInviteHandler.acceptInvite
|
||||
.calledWith(@project_id, @token)
|
||||
.should.equal true
|
||||
|
||||
it 'should have called emitToRoom', ->
|
||||
@EditorRealTimeController.emitToRoom.callCount.should.equal 1
|
||||
|
@ -567,7 +567,7 @@ describe "CollaboratorsInviteController", ->
|
|||
|
||||
beforeEach ->
|
||||
@err = new Error('woops')
|
||||
@CollaboratorsInviteHandler.acceptInvite = sinon.stub().callsArgWith(4, @err)
|
||||
@CollaboratorsInviteHandler.acceptInvite = sinon.stub().callsArgWith(3, @err)
|
||||
@CollaboratorsInviteController.acceptInvite @req, @res, @next
|
||||
|
||||
it 'should not redirect to project page', ->
|
||||
|
|
|
@ -404,7 +404,7 @@ describe "CollaboratorsInviteHandler", ->
|
|||
@CollaboratorsInviteHandler._tryCancelInviteNotification = sinon.stub().callsArgWith(1, null)
|
||||
@ProjectInvite.remove.callsArgWith(1, null)
|
||||
@call = (callback) =>
|
||||
@CollaboratorsInviteHandler.acceptInvite @projectId, @inviteId, @token, @user, callback
|
||||
@CollaboratorsInviteHandler.acceptInvite @projectId, @token, @user, callback
|
||||
|
||||
afterEach ->
|
||||
@_getInviteByToken.restore()
|
||||
|
|
Loading…
Reference in a new issue