ShareProjectModalController.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. define(['base'], App => {
  2. App.controller('ShareProjectModalController', function(
  3. $scope,
  4. $modalInstance,
  5. $timeout,
  6. projectMembers,
  7. projectInvites,
  8. $modal,
  9. $http,
  10. ide,
  11. validateCaptcha,
  12. validateCaptchaV3,
  13. settings,
  14. // eslint-disable-next-line camelcase
  15. eventTracking
  16. ) {
  17. $scope.inputs = {
  18. privileges: 'readAndWrite',
  19. contacts: []
  20. }
  21. $scope.state = {
  22. error: null,
  23. errorReason: null,
  24. inflight: false,
  25. startedFreeTrial: false,
  26. invites: []
  27. }
  28. $modalInstance.opened.then(() =>
  29. $timeout(() => $scope.$broadcast('open'), 200)
  30. )
  31. const INFINITE_COLLABORATORS = -1
  32. $scope.refreshCanAddCollaborators = function() {
  33. const allowedNoOfMembers = $scope.project.features.collaborators
  34. $scope.canAddCollaborators =
  35. $scope.project.members.length + $scope.project.invites.length <
  36. allowedNoOfMembers || allowedNoOfMembers === INFINITE_COLLABORATORS
  37. }
  38. $scope.refreshCanAddCollaborators()
  39. $scope.$watch('canAddCollaborators', function() {
  40. if (!$scope.canAddCollaborators) {
  41. eventTracking.send(
  42. 'subscription-funnel',
  43. 'editor-click-feature',
  44. 'projectMembers'
  45. )
  46. }
  47. })
  48. $scope.$watch(
  49. '(project.members.length + project.invites.length)',
  50. _noOfMembers => $scope.refreshCanAddCollaborators()
  51. )
  52. $scope.autocompleteContacts = []
  53. $http.get('/user/contacts').then(function(response) {
  54. const { data } = response
  55. $scope.autocompleteContacts = data.contacts || []
  56. for (let contact of $scope.autocompleteContacts) {
  57. if (contact.type === 'user') {
  58. if (
  59. contact.first_name === contact.email.split('@')[0] &&
  60. !contact.last_name
  61. ) {
  62. // User has not set their proper name so use email as canonical display property
  63. contact.display = contact.email
  64. } else {
  65. contact.name = `${contact.first_name} ${contact.last_name}`
  66. contact.display = `${contact.name} <${contact.email}>`
  67. }
  68. } else {
  69. // Must be a group
  70. contact.display = contact.name
  71. }
  72. }
  73. })
  74. const getCurrentMemberEmails = () =>
  75. ($scope.project.members || []).map(u => u.email)
  76. const getCurrentInviteEmails = () =>
  77. ($scope.project.invites || []).map(u => u.email)
  78. $scope.filterAutocompleteUsers = function($query) {
  79. const currentMemberEmails = getCurrentMemberEmails()
  80. return $scope.autocompleteContacts.filter(function(contact) {
  81. if (
  82. contact.email != null &&
  83. currentMemberEmails.includes(contact.email)
  84. ) {
  85. return false
  86. }
  87. for (let text of [contact.name, contact.email]) {
  88. if (
  89. text != null &&
  90. text.toLowerCase().indexOf($query.toLowerCase()) > -1
  91. ) {
  92. return true
  93. }
  94. }
  95. return false
  96. })
  97. }
  98. $scope.addMembers = function() {
  99. const addMembers = function() {
  100. if ($scope.inputs.contacts.length === 0) {
  101. return
  102. }
  103. const members = $scope.inputs.contacts
  104. $scope.inputs.contacts = []
  105. $scope.clearError()
  106. $scope.state.inflight = true
  107. if ($scope.project.invites == null) {
  108. $scope.project.invites = []
  109. }
  110. const currentMemberEmails = getCurrentMemberEmails()
  111. const currentInviteEmails = getCurrentInviteEmails()
  112. addNextMember()
  113. function addNextMember() {
  114. let email
  115. if (members.length === 0 || !$scope.canAddCollaborators) {
  116. $scope.state.inflight = false
  117. $scope.$apply()
  118. return
  119. }
  120. const member = members.shift()
  121. if (member.type === 'user') {
  122. email = member.email
  123. } else {
  124. // Not an auto-complete object, so email == display
  125. email = member.display
  126. }
  127. email = email.toLowerCase()
  128. if (currentMemberEmails.includes(email)) {
  129. // Skip this existing member
  130. return addNextMember()
  131. }
  132. // do v3 captcha to collect data only
  133. validateCaptchaV3('invite')
  134. // do v2 captcha
  135. const ExposedSettings = window.ExposedSettings
  136. validateCaptcha(function(response) {
  137. $scope.grecaptchaResponse = response
  138. const invites = $scope.project.invites || []
  139. const invite = _.find(invites, invite => invite.email === email)
  140. let request
  141. if (currentInviteEmails.includes(email) && invite) {
  142. request = projectInvites.resendInvite(invite._id)
  143. } else {
  144. request = projectInvites.sendInvite(
  145. email,
  146. $scope.inputs.privileges,
  147. $scope.grecaptchaResponse
  148. )
  149. }
  150. request
  151. .then(function(response) {
  152. const { data } = response
  153. if (data.error) {
  154. $scope.setError(data.error)
  155. $scope.state.inflight = false
  156. } else {
  157. if (data.invite) {
  158. const { invite } = data
  159. $scope.project.invites.push(invite)
  160. } else {
  161. const users =
  162. data.users != null
  163. ? data.users
  164. : data.user != null
  165. ? [data.user]
  166. : []
  167. $scope.project.members.push(...users)
  168. }
  169. }
  170. setTimeout(
  171. () =>
  172. // Give $scope a chance to update $scope.canAddCollaborators
  173. // with new collaborator information.
  174. addNextMember(),
  175. 0
  176. )
  177. })
  178. .catch(function(httpResponse) {
  179. const { data } = httpResponse
  180. $scope.state.inflight = false
  181. $scope.setError(data.errorReason)
  182. })
  183. }, ExposedSettings.recaptchaDisabled.invite)
  184. }
  185. }
  186. $timeout(addMembers, 50) // Give email list a chance to update
  187. }
  188. $scope.removeMember = function(member) {
  189. $scope.monitorRequest(
  190. projectMembers.removeMember(member).then(function() {
  191. const index = $scope.project.members.indexOf(member)
  192. if (index === -1) {
  193. return
  194. }
  195. $scope.project.members.splice(index, 1)
  196. })
  197. )
  198. }
  199. $scope.revokeInvite = function(invite) {
  200. $scope.monitorRequest(
  201. projectInvites.revokeInvite(invite._id).then(function() {
  202. const index = $scope.project.invites.indexOf(invite)
  203. if (index === -1) {
  204. return
  205. }
  206. $scope.project.invites.splice(index, 1)
  207. })
  208. )
  209. }
  210. $scope.resendInvite = function(invite, event) {
  211. $scope.monitorRequest(
  212. projectInvites
  213. .resendInvite(invite._id)
  214. .then(function() {
  215. event.target.blur()
  216. })
  217. .catch(function() {
  218. event.target.blur()
  219. })
  220. )
  221. }
  222. $scope.makeTokenBased = function() {
  223. $scope.project.publicAccesLevel = 'tokenBased'
  224. settings.saveProjectAdminSettings({ publicAccessLevel: 'tokenBased' })
  225. eventTracking.sendMB('project-make-token-based')
  226. }
  227. $scope.makePrivate = function() {
  228. $scope.project.publicAccesLevel = 'private'
  229. settings.saveProjectAdminSettings({ publicAccessLevel: 'private' })
  230. }
  231. $scope.$watch('project.tokens.readAndWrite', function(token) {
  232. if (token != null) {
  233. $scope.readAndWriteTokenLink = `${location.origin}/${token}`
  234. } else {
  235. $scope.readAndWriteTokenLink = null
  236. }
  237. })
  238. $scope.$watch('project.tokens.readOnly', function(token) {
  239. if (token != null) {
  240. $scope.readOnlyTokenLink = `${location.origin}/read/${token}`
  241. } else {
  242. $scope.readOnlyTokenLink = null
  243. }
  244. })
  245. $scope.done = () => $modalInstance.close()
  246. $scope.cancel = () => $modalInstance.dismiss()
  247. $scope.monitorRequest = function monitorRequest(request) {
  248. $scope.clearError()
  249. $scope.state.inflight = true
  250. return request
  251. .then(() => {
  252. $scope.state.inflight = false
  253. $scope.clearError()
  254. })
  255. .catch(err => {
  256. $scope.state.inflight = false
  257. $scope.setError(err.data && err.data.error)
  258. })
  259. }
  260. $scope.clearError = function clearError() {
  261. $scope.state.error = false
  262. }
  263. $scope.setError = function setError(reason) {
  264. $scope.state.error = true
  265. $scope.state.errorReason = reason
  266. }
  267. })
  268. })