ShareProjectModalController.js 8.2 KB

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