diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js
index d85f325abd..597a8c5d5f 100644
--- a/services/web/app/src/Features/Project/ProjectController.js
+++ b/services/web/app/src/Features/Project/ProjectController.js
@@ -887,10 +887,6 @@ const ProjectController = {
'new_navigation_ui',
user.alphaProgram
),
- showReactShareModal: shouldDisplayFeature(
- 'new_share_modal_ui',
- true
- ),
showReactDropboxModal: shouldDisplayFeature(
'new_dropbox_modal_ui',
user.betaProgram
diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug
index fc05f46c5d..ba8c673f1a 100644
--- a/services/web/app/views/project/editor.pug
+++ b/services/web/app/views/project/editor.pug
@@ -81,7 +81,6 @@ block content
else
include ./editor/header
- include ./editor/share
!= moduleIncludes("publish:body", locals)
include ./editor/history/toolbarV2.pug
diff --git a/services/web/app/views/project/editor/header-react.pug b/services/web/app/views/project/editor/header-react.pug
index 868e119c64..1382120b56 100644
--- a/services/web/app/views/project/editor/header-react.pug
+++ b/services/web/app/views/project/editor/header-react.pug
@@ -1,10 +1,9 @@
-div(ng-controller=showReactShareModal ? 'ReactShareProjectModalController': 'ShareController')
- if showReactShareModal
- share-project-modal(
- handle-hide="handleHide"
- show="show"
- is-admin="isAdmin"
- )
+div(ng-controller="ReactShareProjectModalController")
+ share-project-modal(
+ handle-hide="handleHide"
+ show="show"
+ is-admin="isAdmin"
+ )
div(ng-controller="EditorNavigationToolbarController")
editor-navigation-toolbar-root(
diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug
index 651fcef9d0..e6951a16e0 100644
--- a/services/web/app/views/project/editor/header.pug
+++ b/services/web/app/views/project/editor/header.pug
@@ -119,17 +119,16 @@ header.toolbar.toolbar-header.toolbar-with-labels(
a.btn.btn-full-height(
href
ng-click="openShareProjectModal(permissions.admin);"
- ng-controller=(showReactShareModal ? 'ReactShareProjectModalController': 'ShareController')
+ ng-controller="ReactShareProjectModalController"
)
i.fa.fa-fw.fa-group
p.toolbar-label #{translate("share")}
- if showReactShareModal
- share-project-modal(
- handle-hide="handleHide"
- show="show"
- is-admin="isAdmin"
- )
+ share-project-modal(
+ handle-hide="handleHide"
+ show="show"
+ is-admin="isAdmin"
+ )
!= moduleIncludes('publish:button', locals)
if !isRestrictedTokenMember
diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug
deleted file mode 100644
index 02d1df3308..0000000000
--- a/services/web/app/views/project/editor/share.pug
+++ /dev/null
@@ -1,287 +0,0 @@
-script(type='text/ng-template', id='shareProjectModalTemplate')
- .modal-header
- button.close(
- type="button"
- data-dismiss="modal"
- ng-click="cancel()"
- aria-label="Close"
- )
- span(aria-hidden="true") ×
- h3 #{translate("share_project")}
- .modal-body.modal-body-share
- .container-fluid
-
- if isRestrictedTokenMember
- //- Token-based access
- .row.public-access-level
- .col-xs-12.access-token-display-area
- div.access-token-wrapper
- strong #{translate('anyone_with_link_can_view')}
- pre.access-token {{ readOnlyTokenLink }}
-
- if !isRestrictedTokenMember
- //- Private (with token-access available)
- .row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'private'")
- .col-xs-12.text-center
- | #{translate('link_sharing_is_off')}
- |
- a(
- href
- ng-click="makeTokenBased()"
- ) #{translate('turn_on_link_sharing')}
- span
- a(
- href="/learn/how-to/What_is_Link_Sharing%3F"
- target="_blank"
- )
- i.fa.fa-question-circle(
- tooltip=translate('learn_more_about_link_sharing')
- )
-
- //- Token-based access
- .row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'tokenBased'")
- .col-xs-12.text-center
- strong
- | #{translate('link_sharing_is_on')}.
- |
- a(
- href
- ng-click="makePrivate()"
- ) #{translate('turn_off_link_sharing')}
- span
- a(
- href="/learn/how-to/What_is_Link_Sharing%3F"
- target="_blank"
- )
- i.fa.fa-question-circle(
- tooltip=translate('learn_more_about_link_sharing')
- )
-
- .col-xs-12.access-token-display-area
- div.access-token-wrapper
- strong #{translate('anyone_with_link_can_edit')}
- pre.access-token(ng-show="readAndWriteTokenLink") {{ readAndWriteTokenLink }}
- pre.access-token(ng-hide="readAndWriteTokenLink") #{translate('loading')}…
- div.access-token-wrapper
- strong #{translate('anyone_with_link_can_view')}
- pre.access-token(ng-show="readOnlyTokenLink") {{ readOnlyTokenLink }}
- pre.access-token(ng-hide="readOnlyTokenLink") #{translate('loading')}…
-
- //- legacy public-access
- .row.public-access-level(ng-show="isAdmin && (project.publicAccesLevel == 'readAndWrite' || project.publicAccesLevel == 'readOnly')")
- .col-xs-12.text-center
- strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")}
- strong(ng-if="project.publicAccesLevel == 'readOnly'") #{translate("this_project_is_public_read_only")}
- |
- a(
- href
- ng-click="makePrivate()"
- ) #{translate("make_private")}
-
- .row.project-member
- .col-xs-7 {{ project.owner.email }}
- .text-left.col-xs-3 #{translate("owner")}
- form.form-horizontal(
- ng-if="isAdmin"
- ng-repeat="member in project.members"
- ng-controller="ShareProjectModalMemberRowController"
- )
- .row.form-group.project-member
- .col-xs-7.form-control-static {{ member.email }}
- .col-xs-3
- select.privileges.form-control.input-sm(name="privileges" ng-model="form.privileges")
- option(value="owner") #{translate("owner")}
- option(value="readAndWrite") #{translate("can_edit")}
- option(value="readOnly") #{translate("read_only")}
- .col-xs-2.form-control-static.text-center(ng-hide="form.isModified()")
- a(
- href
- tooltip=translate('remove_collaborator')
- tooltip-placement="bottom"
- ng-click="removeMember(member)"
- aria-label=translate('remove_collaborator')
- )
- i.fa.fa-times
- .col-xs-2.text-center(ng-show="form.isModified()")
- button.btn.btn-sm.btn-success(
- type="submit"
- ng-click="form.submit()"
- ) #{translate("change")}
- .text-sm
- | #{translate("or")}
- |
- button.btn.btn-inline-link(ng-click="form.reset()") #{translate("cancel").toLowerCase()}
-
- .row.project-member(ng-if="!isAdmin" ng-repeat="member in project.members")
- .col-xs-7 {{ member.email }}
- .col-xs-3
- span(ng-if="member.privileges == 'readAndWrite'") #{translate("can_edit")}
- span(ng-if="member.privileges == 'readOnly'") #{translate("read_only")}
- .row.project-invite(ng-repeat="invite in project.invites")
- .col-xs-7 {{ invite.email }}
- div.small
- | #{translate("invite_not_accepted")}.
- button.btn.btn-inline-link(
- ng-show="isAdmin",
- ng-click="resendInvite(invite, $event)"
- ) #{translate("resend")}
- .col-xs-3.text-left
- // todo: get invite privileges
- span(ng-show="invite.privileges == 'readAndWrite'") #{translate("can_edit")}
- span(ng-show="invite.privileges == 'readOnly'") #{translate("read_only")}
- .col-xs-2.text-center(ng-if="isAdmin")
- a(
- href
- tooltip=translate('revoke_invite')
- tooltip-placement="bottom"
- ng-click="revokeInvite(invite)"
- )
- i.fa.fa-times
- .row.invite-controls(ng-show="isAdmin")
- form(ng-show="canAddCollaborators")
- .small #{translate("share_with_your_collabs")}
- .form-group
- tags-input(
- class="tags-input"
- template="shareTagTemplate"
- placeholder=settings.customisation.shareProjectPlaceholder || 'joe@example.com, sue@example.com, …'
- ng-model="inputs.contacts"
- focus-on="open"
- display-property="display"
- add-on-paste="true"
- add-on-enter="false"
- replace-spaces-with-dashes="false"
- type="email"
- )
- auto-complete(
- source="filterAutocompleteUsers($query)"
- template="shareAutocompleteTemplate"
- display-property="email"
- min-length="0"
- )
- .form-group
- .pull-right
- select.privileges.form-control(
- ng-model="inputs.privileges"
- name="privileges"
- )
- option(value="readAndWrite") #{translate("can_edit")}
- option(value="readOnly") #{translate("read_only")}
- |
- //- We have to use mousedown here since click has issues with the
- //- blur handler in tags-input sometimes changing its height and
- //- moving this button, preventing the click registering.
- button.btn.btn-info(
- type="submit"
- ng-mousedown="addMembers()"
- ng-keyup="$event.keyCode == 13 ? addMembers() : null"
- ) #{translate("share")}
- div(ng-hide="canAddCollaborators")
- p.text-center #{translate("need_to_upgrade_for_more_collabs")}. Also:
- .row
- .col-md-8.col-md-offset-2
- ul.list-unstyled
- li
- i.fa.fa-check
- | #{translate("unlimited_projects")}
-
- li
- i.fa.fa-check
- | #{translate("collabs_per_proj", {collabcount:'Multiple'})}
-
- li
- i.fa.fa-check
- | #{translate("full_doc_history")}
-
- li
- i.fa.fa-check
- | #{translate("sync_to_dropbox")}
-
- li
- i.fa.fa-check
- | #{translate("sync_to_github")}
-
- li
- i.fa.fa-check
- |#{translate("compile_larger_projects")}
- p.text-center.row-spaced-thin(ng-show="user.allowedFreeTrial" ng-controller="FreeTrialModalController")
- a.btn.btn-success(
- href
- ng-class="buttonClass"
- ng-click="startFreeTrial('projectMembers')"
- ) #{translate("start_free_trial")}
-
- p.text-center.row-spaced-thin(ng-show="!user.allowedFreeTrial" ng-controller="UpgradeModalController")
- a.btn.btn-success(
- href
- ng-class="buttonClass"
- ng-click="upgradePlan('projectMembers')"
- ) #{translate("upgrade")}
-
- p.small(ng-show="startedFreeTrial")
- | #{translate("refresh_page_after_starting_free_trial")}
- .row.public-access-level.public-access-level--notice(ng-show="!isAdmin")
- .col-xs-12.text-center(ng-show="project.publicAccesLevel == 'private'") #{translate("to_add_more_collaborators")}
- .col-xs-12.text-center(ng-show="project.publicAccesLevel == 'tokenBased'") #{translate("to_change_access_permissions")}
- .modal-footer.modal-footer-share
- .modal-footer-left
- i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
- span.text-danger.error(ng-show="state.error")
- span(ng-switch="state.errorReason")
- span(ng-switch-when="cannot_invite_non_user")
- | #{translate("cannot_invite_non_user")}
- span(ng-switch-when="cannot_verify_user_not_robot")
- | #{translate("cannot_verify_user_not_robot")}
- span(ng-switch-when="cannot_invite_self")
- | #{translate("cannot_invite_self")}
- span(ng-switch-when="invalid_email")
- | #{translate("invalid_email")}
- span(ng-switch-default)
- | #{translate("generic_something_went_wrong")}
- .modal-footer-right
- button.btn.btn-default(
- ng-click="done()"
- ) #{translate("close")}
-
-script(type="text/ng-template", id="shareTagTemplate")
- .tag-template
- span(ng-if="data.type")
- i.fa.fa-fw(ng-class="{'fa-user': data.type != 'group', 'fa-group': data.type == 'group'}")
- |
- span {{$getDisplayText()}}
- |
- a(href, ng-click="$removeTag()").remove-button
- i.fa.fa-fw.fa-close
-
-script(type="text/ng-template", id="shareAutocompleteTemplate")
- .autocomplete-template
- div(ng-if="data.type == 'user'")
- i.fa.fa-fw.fa-user
- |
- span(ng-bind-html="$highlight(data.display)")
- div(ng-if="data.type == 'group'")
- i.fa.fa-fw.fa-group
- |
- span(ng-bind-html="$highlight(data.name)")
- span.subdued.small(ng-show="data.member_count") ({{ data.member_count }} members)
-
-script(type="text/ng-template", id="ownershipTransferConfirmTemplate")
- .modal-header
- button.close(
- type="button"
- data-dismiss="modal"
- ng-click="cancel()"
- aria-label="Close"
- )
- span(aria-hidden="true") ×
- h3 #{translate("change_project_owner")}
- .modal-body
- p !{translate('project_ownership_transfer_confirmation_1', { user: '{{ member.email }}', project: '{{ project.name }}' }, ['strong', 'strong'])}
- p #{translate('project_ownership_transfer_confirmation_2')}
- .modal-footer
- .modal-footer-left
- i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
- span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
- .modal-footer-right
- button.btn.btn-default(ng-click="cancel()") #{translate("cancel")}
- button.btn.btn-success(ng-click="confirm()") #{translate("change_owner")}
diff --git a/services/web/frontend/js/base.js b/services/web/frontend/js/base.js
index 2082f5e3ba..60f0b38da3 100644
--- a/services/web/frontend/js/base.js
+++ b/services/web/frontend/js/base.js
@@ -35,7 +35,6 @@ const App = angular
'ErrorCatcher',
'localStorage',
'sessionStorage',
- 'ngTagsInput',
'ui.select',
])
.config(function ($qProvider, $httpProvider, uiSelectConfig) {
diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js
index ebc808ae73..0da7f26f3c 100644
--- a/services/web/frontend/js/ide.js
+++ b/services/web/frontend/js/ide.js
@@ -35,7 +35,6 @@ import SafariScrollPatcher from './ide/SafariScrollPatcher'
import { loadServiceWorker } from './ide/pdfng/directives/serviceWorkerManager'
import './ide/cobranding/CobrandingDataService'
import './ide/settings/index'
-import './ide/share/index'
import './ide/binary-files/index'
import './ide/chat/index'
import './ide/clone/index'
@@ -64,6 +63,7 @@ import './main/system-messages'
import '../../modules/modules-ide.js'
import './shared/context/controllers/root-context-controller'
import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller'
+import './features/share-project-modal/controllers/react-share-project-modal-controller'
import getMeta from './utils/meta'
App.controller(
diff --git a/services/web/frontend/js/ide/share/controllers/OwnershipTransferConfirmModalController.js b/services/web/frontend/js/ide/share/controllers/OwnershipTransferConfirmModalController.js
deleted file mode 100644
index b5bdc4cc55..0000000000
--- a/services/web/frontend/js/ide/share/controllers/OwnershipTransferConfirmModalController.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import App from '../../../base'
-App.controller(
- 'OwnershipTransferConfirmModalController',
- function ($scope, $window, $modalInstance, projectMembers) {
- $scope.state = {
- inflight: false,
- error: false,
- }
-
- $scope.confirm = function () {
- const userId = $scope.member._id
- transferOwnership(userId)
- }
-
- $scope.cancel = function () {
- $modalInstance.dismiss()
- }
-
- function transferOwnership(userId) {
- $scope.state.inflight = true
- $scope.state.error = false
- projectMembers
- .transferOwnership(userId)
- .then(() => {
- $scope.state.inflight = false
- $scope.state.error = false
- $window.location.reload()
- })
- .catch(() => {
- $scope.state.inflight = false
- $scope.state.error = true
- })
- }
- }
-)
diff --git a/services/web/frontend/js/ide/share/controllers/ShareController.js b/services/web/frontend/js/ide/share/controllers/ShareController.js
deleted file mode 100644
index b8f4b0b655..0000000000
--- a/services/web/frontend/js/ide/share/controllers/ShareController.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import App from '../../../base'
-App.controller('ShareController', function (
- $scope,
- $modal,
- ide,
- projectInvites,
- projectMembers,
- // eslint-disable-next-line camelcase
- eventTracking
-) {
- $scope.openShareProjectModal = function (isAdmin) {
- $scope.isAdmin = isAdmin
- eventTracking.sendMBOnce('ide-open-share-modal-once')
-
- $modal.open({
- templateUrl: 'shareProjectModalTemplate',
- controller: 'ShareProjectModalController',
- scope: $scope,
- })
- }
-
- ide.socket.on('project:tokens:changed', data => {
- if (data.tokens != null) {
- ide.$scope.project.tokens = data.tokens
- $scope.$digest()
- }
- })
-
- ide.socket.on('project:membership:changed', data => {
- if (data.members) {
- projectMembers
- .getMembers()
- .then(response => {
- if (response.data.members) {
- $scope.project.members = response.data.members
- }
- })
- .catch(() => {
- console.error('Error fetching members for project')
- })
- }
- if (data.invites) {
- projectInvites
- .getInvites()
- .then(response => {
- if (response.data.invites) {
- $scope.project.invites = response.data.invites
- }
- })
- .catch(() => {
- console.error('Error fetching invites for project')
- })
- }
- })
-})
diff --git a/services/web/frontend/js/ide/share/controllers/ShareProjectModalController.js b/services/web/frontend/js/ide/share/controllers/ShareProjectModalController.js
deleted file mode 100644
index 944bfcc8c1..0000000000
--- a/services/web/frontend/js/ide/share/controllers/ShareProjectModalController.js
+++ /dev/null
@@ -1,309 +0,0 @@
-import _ from 'lodash'
-import App from '../../../base'
-App.controller('ShareProjectModalController', function (
- $scope,
- $modalInstance,
- $timeout,
- projectMembers,
- projectInvites,
- $modal,
- $http,
- ide,
- validateCaptcha,
- validateCaptchaV3,
- settings,
- // eslint-disable-next-line camelcase
- eventTracking
-) {
- $scope.inputs = {
- privileges: 'readAndWrite',
- contacts: [],
- }
- $scope.state = {
- error: null,
- errorReason: null,
- inflight: false,
- startedFreeTrial: false,
- invites: [],
- }
-
- $modalInstance.opened.then(() =>
- $timeout(() => $scope.$broadcast('open'), 200)
- )
-
- const INFINITE_COLLABORATORS = -1
-
- $scope.refreshCanAddCollaborators = function () {
- const allowedNoOfMembers = $scope.project.features.collaborators
- $scope.canAddCollaborators =
- $scope.project.members.length + $scope.project.invites.length <
- allowedNoOfMembers || allowedNoOfMembers === INFINITE_COLLABORATORS
- }
- $scope.refreshCanAddCollaborators()
-
- $scope.$watch('canAddCollaborators', function () {
- if (!$scope.canAddCollaborators) {
- eventTracking.send(
- 'subscription-funnel',
- 'editor-click-feature',
- 'projectMembers'
- )
- }
- })
-
- $scope.$watch(
- '(project.members.length + project.invites.length)',
- _noOfMembers => $scope.refreshCanAddCollaborators()
- )
-
- $scope.autocompleteContacts = []
- if ($scope.isRestrictedTokenMember) {
- // Restricted token members are users who join via a read-only link.
- // They will not be able to invite any users, so skip the lookup of
- // their contacts. This request would result in a 403 for anonymous
- // users, which in turn would redirect them to the /login.
- } else {
- $http.get('/user/contacts').then(processContactsResponse)
- }
-
- function processContactsResponse(response) {
- const { data } = response
- $scope.autocompleteContacts = data.contacts || []
- for (const contact of $scope.autocompleteContacts) {
- if (contact.type === 'user') {
- if (
- contact.first_name === contact.email.split('@')[0] &&
- !contact.last_name
- ) {
- // User has not set their proper name so use email as canonical display property
- contact.display = contact.email
- } else {
- contact.name = `${contact.first_name} ${contact.last_name}`
- contact.display = `${contact.name} <${contact.email}>`
- }
- } else {
- // Must be a group
- contact.display = contact.name
- }
- }
- }
-
- const getCurrentMemberEmails = () =>
- ($scope.project.members || []).map(u => u.email)
-
- const getCurrentInviteEmails = () =>
- ($scope.project.invites || []).map(u => u.email)
-
- $scope.filterAutocompleteUsers = function ($query) {
- const currentMemberEmails = getCurrentMemberEmails()
- return $scope.autocompleteContacts.filter(function (contact) {
- if (
- contact.email != null &&
- currentMemberEmails.includes(contact.email)
- ) {
- return false
- }
- for (const text of [contact.name, contact.email]) {
- if (
- text != null &&
- text.toLowerCase().indexOf($query.toLowerCase()) > -1
- ) {
- return true
- }
- }
- return false
- })
- }
-
- $scope.addMembers = function () {
- const addMembers = function () {
- if ($scope.inputs.contacts.length === 0) {
- return
- }
-
- const members = $scope.inputs.contacts
- $scope.inputs.contacts = []
- $scope.clearError()
- $scope.state.inflight = true
-
- if ($scope.project.invites == null) {
- $scope.project.invites = []
- }
-
- const currentMemberEmails = getCurrentMemberEmails()
- const currentInviteEmails = getCurrentInviteEmails()
- addNextMember()
-
- function addNextMember() {
- let email
- if (members.length === 0 || !$scope.canAddCollaborators) {
- $scope.state.inflight = false
- $scope.$apply()
- return
- }
-
- const member = members.shift()
- if (member.type === 'user') {
- email = member.email
- } else {
- // Not an auto-complete object, so email == display
- email = member.display
- }
- email = email.toLowerCase()
-
- if (currentMemberEmails.includes(email)) {
- // Skip this existing member
- return addNextMember()
- }
- // do v3 captcha to collect data only
- validateCaptchaV3('invite')
- // do v2 captcha
- const ExposedSettings = window.ExposedSettings
- validateCaptcha(function (response) {
- $scope.grecaptchaResponse = response
- const invites = $scope.project.invites || []
- const invite = _.find(invites, invite => invite.email === email)
- let request
- if (currentInviteEmails.includes(email) && invite) {
- request = projectInvites.resendInvite(invite._id)
- } else {
- request = projectInvites.sendInvite(
- email,
- $scope.inputs.privileges,
- $scope.grecaptchaResponse
- )
- }
-
- request
- .then(function (response) {
- const { data } = response
- if (data.error) {
- $scope.setError(data.error)
- $scope.state.inflight = false
- } else {
- if (data.invite) {
- const { invite } = data
- $scope.project.invites.push(invite)
- } else {
- const users =
- data.users != null
- ? data.users
- : data.user != null
- ? [data.user]
- : []
- $scope.project.members.push(...users)
- }
- }
-
- setTimeout(
- () =>
- // Give $scope a chance to update $scope.canAddCollaborators
- // with new collaborator information.
- addNextMember(),
-
- 0
- )
- })
- .catch(function (httpResponse) {
- const { data } = httpResponse
- $scope.state.inflight = false
- $scope.setError(data.errorReason)
- })
- }, ExposedSettings.recaptchaDisabled.invite)
- }
- }
-
- $timeout(addMembers, 50) // Give email list a chance to update
- }
-
- $scope.removeMember = function (member) {
- $scope.monitorRequest(
- projectMembers.removeMember(member).then(function () {
- const index = $scope.project.members.indexOf(member)
- if (index === -1) {
- return
- }
- $scope.project.members.splice(index, 1)
- })
- )
- }
-
- $scope.revokeInvite = function (invite) {
- $scope.monitorRequest(
- projectInvites.revokeInvite(invite._id).then(function () {
- const index = $scope.project.invites.indexOf(invite)
- if (index === -1) {
- return
- }
- $scope.project.invites.splice(index, 1)
- })
- )
- }
-
- $scope.resendInvite = function (invite, event) {
- $scope.monitorRequest(
- projectInvites
- .resendInvite(invite._id)
- .then(function () {
- event.target.blur()
- })
- .catch(function () {
- event.target.blur()
- })
- )
- }
-
- $scope.makeTokenBased = function () {
- $scope.project.publicAccesLevel = 'tokenBased'
- settings.saveProjectAdminSettings({ publicAccessLevel: 'tokenBased' })
- eventTracking.sendMB('project-make-token-based')
- }
-
- $scope.makePrivate = function () {
- $scope.project.publicAccesLevel = 'private'
- settings.saveProjectAdminSettings({ publicAccessLevel: 'private' })
- }
-
- $scope.$watch('project.tokens.readAndWrite', function (token) {
- if (token != null) {
- $scope.readAndWriteTokenLink = `${location.origin}/${token}`
- } else {
- $scope.readAndWriteTokenLink = null
- }
- })
-
- $scope.$watch('project.tokens.readOnly', function (token) {
- if (token != null) {
- $scope.readOnlyTokenLink = `${location.origin}/read/${token}`
- } else {
- $scope.readOnlyTokenLink = null
- }
- })
-
- $scope.done = () => $modalInstance.close()
-
- $scope.cancel = () => $modalInstance.dismiss()
-
- $scope.monitorRequest = function monitorRequest(request) {
- $scope.clearError()
- $scope.state.inflight = true
- return request
- .then(() => {
- $scope.state.inflight = false
- $scope.clearError()
- })
- .catch(err => {
- $scope.state.inflight = false
- $scope.setError(err.data && err.data.error)
- })
- }
-
- $scope.clearError = function clearError() {
- $scope.state.error = false
- }
-
- $scope.setError = function setError(reason) {
- $scope.state.error = true
- $scope.state.errorReason = reason
- }
-})
diff --git a/services/web/frontend/js/ide/share/controllers/ShareProjectModalMemberRowController.js b/services/web/frontend/js/ide/share/controllers/ShareProjectModalMemberRowController.js
deleted file mode 100644
index 0741138b5f..0000000000
--- a/services/web/frontend/js/ide/share/controllers/ShareProjectModalMemberRowController.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import App from '../../../base'
-App.controller(
- 'ShareProjectModalMemberRowController',
- function ($scope, $modal, projectMembers) {
- $scope.form = {
- privileges: $scope.member.privileges,
-
- isModified() {
- return this.privileges !== $scope.member.privileges
- },
-
- submit() {
- const userId = $scope.member._id
- const privilegeLevel = $scope.form.privileges
- if (privilegeLevel === 'owner') {
- openOwnershipTransferConfirmModal(userId)
- } else {
- setPrivilegeLevel(userId, privilegeLevel)
- }
- },
-
- reset() {
- this.privileges = $scope.member.privileges
- $scope.clearError()
- },
- }
-
- function setPrivilegeLevel(userId, privilegeLevel) {
- $scope.monitorRequest(
- projectMembers
- .setMemberPrivilegeLevel(userId, privilegeLevel)
- .then(() => {
- $scope.member.privileges = privilegeLevel
- })
- )
- }
-
- function openOwnershipTransferConfirmModal(userId) {
- $modal.open({
- templateUrl: 'ownershipTransferConfirmTemplate',
- controller: 'OwnershipTransferConfirmModalController',
- scope: $scope,
- })
- }
- }
-)
diff --git a/services/web/frontend/js/ide/share/index.js b/services/web/frontend/js/ide/share/index.js
deleted file mode 100644
index 6ced21a543..0000000000
--- a/services/web/frontend/js/ide/share/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import './controllers/ShareController'
-import './controllers/ShareProjectModalController'
-import './controllers/ShareProjectModalMemberRowController'
-import './controllers/OwnershipTransferConfirmModalController'
-import './services/projectMembers'
-import './services/projectInvites'
-import '../../features/share-project-modal/controllers/react-share-project-modal-controller'
diff --git a/services/web/frontend/js/ide/share/services/projectInvites.js b/services/web/frontend/js/ide/share/services/projectInvites.js
deleted file mode 100644
index 041905f2fa..0000000000
--- a/services/web/frontend/js/ide/share/services/projectInvites.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import App from '../../../base'
-
-export default App.factory('projectInvites', (ide, $http) => ({
- sendInvite(email, privileges, grecaptchaResponse) {
- return $http.post(`/project/${ide.project_id}/invite`, {
- email,
- privileges,
- _csrf: window.csrfToken,
- 'g-recaptcha-response': grecaptchaResponse,
- })
- },
-
- revokeInvite(inviteId) {
- return $http({
- url: `/project/${ide.project_id}/invite/${inviteId}`,
- method: 'DELETE',
- headers: {
- 'X-Csrf-Token': window.csrfToken,
- },
- })
- },
-
- resendInvite(inviteId, privileges) {
- return $http.post(`/project/${ide.project_id}/invite/${inviteId}/resend`, {
- _csrf: window.csrfToken,
- })
- },
-
- getInvites() {
- return $http.get(`/project/${ide.project_id}/invites`, {
- json: true,
- headers: {
- 'X-Csrf-Token': window.csrfToken,
- },
- })
- },
-}))
diff --git a/services/web/frontend/js/ide/share/services/projectMembers.js b/services/web/frontend/js/ide/share/services/projectMembers.js
deleted file mode 100644
index 5cdd524dd7..0000000000
--- a/services/web/frontend/js/ide/share/services/projectMembers.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import App from '../../../base'
-App.factory('projectMembers', (ide, $http) => ({
- removeMember(member) {
- return $http({
- url: `/project/${ide.project_id}/users/${member._id}`,
- method: 'DELETE',
- headers: {
- 'X-Csrf-Token': window.csrfToken,
- },
- })
- },
-
- addGroup(groupId, privileges) {
- return $http.post(`/project/${ide.project_id}/group`, {
- group_id: groupId,
- privileges,
- _csrf: window.csrfToken,
- })
- },
-
- getMembers() {
- return $http.get(`/project/${ide.project_id}/members`, {
- json: true,
- headers: {
- 'X-Csrf-Token': window.csrfToken,
- },
- })
- },
-
- setMemberPrivilegeLevel(userId, privilegeLevel) {
- return $http.put(
- `/project/${ide.project_id}/users/${userId}`,
- { privilegeLevel },
- {
- headers: {
- 'X-Csrf-Token': window.csrfToken,
- },
- }
- )
- },
-
- transferOwnership(userId) {
- return $http.post(`/project/${ide.project_id}/transfer-ownership`, {
- user_id: userId,
- _csrf: window.csrfToken,
- })
- },
-}))
diff --git a/services/web/frontend/js/libraries.js b/services/web/frontend/js/libraries.js
index 4a931c6ecd..87ac1df89c 100644
--- a/services/web/frontend/js/libraries.js
+++ b/services/web/frontend/js/libraries.js
@@ -8,7 +8,6 @@ import 'libs/ng-context-menu-0.1.4'
import 'libs/jquery.storage'
import 'libs/angular-cookie'
import 'libs/passfield'
-import 'libs/ng-tags-input-3.0.0'
import 'libs/select/select'
// CSS
diff --git a/services/web/frontend/js/vendor/libs/ng-tags-input-3.0.0.js b/services/web/frontend/js/vendor/libs/ng-tags-input-3.0.0.js
deleted file mode 100644
index fb5f2d7fb4..0000000000
--- a/services/web/frontend/js/vendor/libs/ng-tags-input-3.0.0.js
+++ /dev/null
@@ -1,1151 +0,0 @@
-/*!
- * ngTagsInput v3.0.0
- * http://mbenford.github.io/ngTagsInput
- *
- * Copyright (c) 2013-2015 Michael Benford
- * License: MIT
- *
- * Generated at 2015-07-13 02:08:11 -0300
- */
-(function() {
-'use strict';
-
-var KEYS = {
- backspace: 8,
- tab: 9,
- enter: 13,
- escape: 27,
- space: 32,
- up: 38,
- down: 40,
- left: 37,
- right: 39,
- delete: 46,
- comma: 188
-};
-
-var MAX_SAFE_INTEGER = 9007199254740991;
-var SUPPORTED_INPUT_TYPES = ['text', 'email', 'url'];
-
-var tagsInput = angular.module('ngTagsInput', []);
-
-/**
- * @ngdoc directive
- * @name tagsInput
- * @module ngTagsInput
- *
- * @description
- * Renders an input box with tag editing support.
- *
- * @param {string} ngModel Assignable Angular expression to data-bind to.
- * @param {string=} [template=NA] URL or id of a custom template for rendering each tag.
- * @param {string=} [displayProperty=text] Property to be rendered as the tag label.
- * @param {string=} [keyProperty=text] Property to be used as a unique identifier for the tag.
- * @param {string=} [type=text] Type of the input element. Only 'text', 'email' and 'url' are supported values.
- * @param {string=} [text=NA] Assignable Angular expression for data-binding to the element's text.
- * @param {number=} tabindex Tab order of the control.
- * @param {string=} [placeholder=Add a tag] Placeholder text for the control.
- * @param {number=} [minLength=3] Minimum length for a new tag.
- * @param {number=} [maxLength=MAX_SAFE_INTEGER] Maximum length allowed for a new tag.
- * @param {number=} [minTags=0] Sets minTags validation error key if the number of tags added is less than minTags.
- * @param {number=} [maxTags=MAX_SAFE_INTEGER] Sets maxTags validation error key if the number of tags added is greater
- * than maxTags.
- * @param {boolean=} [allowLeftoverText=false] Sets leftoverText validation error key if there is any leftover text in
- * the input element when the directive loses focus.
- * @param {string=} [removeTagSymbol=×] (Obsolete) Symbol character for the remove tag button.
- * @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key.
- * @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key.
- * @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key.
- * @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus.
- * @param {boolean=} [addOnPaste=false] Flag indicating that the text pasted into the input field will be split into tags.
- * @param {string=} [pasteSplitPattern=,] Regular expression used to split the pasted text into tags.
- * @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes.
- * @param {string=} [allowedTagsPattern=.+] Regular expression that determines whether a new tag is valid.
- * @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into the new tag
- * input box instead of being removed when the backspace key is pressed and the input box is empty.
- * @param {boolean=} [addFromAutocompleteOnly=false] Flag indicating that only tags coming from the autocomplete list
- * will be allowed. When this flag is true, addOnEnter, addOnComma, addOnSpace and addOnBlur values are ignored.
- * @param {boolean=} [spellcheck=true] Flag indicating whether the browser's spellcheck is enabled for the input field or not.
- * @param {expression=} [onTagAdding=NA] Expression to evaluate that will be invoked before adding a new tag. The new
- * tag is available as $tag. This method must return either true or false. If false, the tag will not be added.
- * @param {expression=} [onTagAdded=NA] Expression to evaluate upon adding a new tag. The new tag is available as $tag.
- * @param {expression=} [onInvalidTag=NA] Expression to evaluate when a tag is invalid. The invalid tag is available as $tag.
- * @param {expression=} [onTagRemoving=NA] Expression to evaluate that will be invoked before removing a tag. The tag
- * is available as $tag. This method must return either true or false. If false, the tag will not be removed.
- * @param {expression=} [onTagRemoved=NA] Expression to evaluate upon removing an existing tag. The removed tag is
- * available as $tag.
- * @param {expression=} [onTagClicked=NA] Expression to evaluate upon clicking an existing tag. The clicked tag is available as $tag.
- */
-tagsInput.directive('tagsInput', ["$timeout", "$document", "$window", "tagsInputConfig", "tiUtil", function($timeout, $document, $window, tagsInputConfig, tiUtil) {
- function TagList(options, events, onTagAdding, onTagRemoving) {
- var self = {}, getTagText, setTagText, tagIsValid;
-
- getTagText = function(tag) {
- return tiUtil.safeToString(tag[options.displayProperty]);
- };
-
- setTagText = function(tag, text) {
- tag[options.displayProperty] = text;
- };
-
- tagIsValid = function(tag) {
- var tagText = getTagText(tag);
-
- return tagText &&
- tagText.length >= options.minLength &&
- tagText.length <= options.maxLength &&
- options.allowedTagsPattern.test(tagText) &&
- !tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty) &&
- onTagAdding({ $tag: tag });
- };
-
- self.items = [];
-
- self.addText = function(text) {
- var tag = {};
- setTagText(tag, text);
- return self.add(tag);
- };
-
- self.add = function(tag) {
- var tagText = getTagText(tag);
-
- if (options.replaceSpacesWithDashes) {
- tagText = tiUtil.replaceSpacesWithDashes(tagText);
- }
-
- setTagText(tag, tagText);
-
- if (tagIsValid(tag)) {
- self.items.push(tag);
- events.trigger('tag-added', { $tag: tag });
- }
- else if (tagText) {
- events.trigger('invalid-tag', { $tag: tag });
- }
-
- return tag;
- };
-
- self.remove = function(index) {
- var tag = self.items[index];
-
- if (onTagRemoving({ $tag: tag })) {
- self.items.splice(index, 1);
- self.clearSelection();
- events.trigger('tag-removed', { $tag: tag });
- return tag;
- }
- };
-
- self.select = function(index) {
- if (index < 0) {
- index = self.items.length - 1;
- }
- else if (index >= self.items.length) {
- index = 0;
- }
-
- self.index = index;
- self.selected = self.items[index];
- };
-
- self.selectPrior = function() {
- self.select(--self.index);
- };
-
- self.selectNext = function() {
- self.select(++self.index);
- };
-
- self.removeSelected = function() {
- return self.remove(self.index);
- };
-
- self.clearSelection = function() {
- self.selected = null;
- self.index = -1;
- };
-
- self.clearSelection();
-
- return self;
- }
-
- function validateType(type) {
- return SUPPORTED_INPUT_TYPES.indexOf(type) !== -1;
- }
-
- return {
- restrict: 'E',
- require: 'ngModel',
- scope: {
- tags: '=ngModel',
- text: '=?',
- onTagAdding: '&',
- onTagAdded: '&',
- onInvalidTag: '&',
- onTagRemoving: '&',
- onTagRemoved: '&',
- onTagClicked: '&'
- },
- replace: false,
- transclude: true,
- templateUrl: 'ngTagsInput/tags-input.html',
- controller: ["$scope", "$attrs", "$element", function($scope, $attrs, $element) {
- $scope.events = tiUtil.simplePubSub();
-
- tagsInputConfig.load('tagsInput', $scope, $attrs, {
- template: [String, 'ngTagsInput/tag-item.html'],
- type: [String, 'text', validateType],
- placeholder: [String, 'Add a tag'],
- tabindex: [Number, null],
- removeTagSymbol: [String, String.fromCharCode(215)],
- replaceSpacesWithDashes: [Boolean, true],
- minLength: [Number, 3],
- maxLength: [Number, MAX_SAFE_INTEGER],
- addOnEnter: [Boolean, true],
- addOnSpace: [Boolean, false],
- addOnComma: [Boolean, true],
- addOnBlur: [Boolean, true],
- addOnPaste: [Boolean, false],
- pasteSplitPattern: [RegExp, /,/],
- allowedTagsPattern: [RegExp, /.+/],
- enableEditingLastTag: [Boolean, false],
- minTags: [Number, 0],
- maxTags: [Number, MAX_SAFE_INTEGER],
- displayProperty: [String, 'text'],
- keyProperty: [String, ''],
- allowLeftoverText: [Boolean, false],
- addFromAutocompleteOnly: [Boolean, false],
- spellcheck: [Boolean, true]
- });
-
- $scope.tagList = new TagList($scope.options, $scope.events,
- tiUtil.handleUndefinedResult($scope.onTagAdding, true),
- tiUtil.handleUndefinedResult($scope.onTagRemoving, true));
-
- this.registerAutocomplete = function() {
- var input = $element.find('input');
-
- return {
- addTag: function(tag) {
- return $scope.tagList.add(tag);
- },
- focusInput: function() {
- input[0].focus();
- },
- getTags: function() {
- return $scope.tagList.items;
- },
- getCurrentTagText: function() {
- return $scope.newTag.text();
- },
- getOptions: function() {
- return $scope.options;
- },
- on: function(name, handler) {
- $scope.events.on(name, handler);
- return this;
- }
- };
- };
-
- this.registerTagItem = function() {
- return {
- getOptions: function() {
- return $scope.options;
- },
- removeTag: function(index) {
- if ($scope.disabled) {
- return;
- }
- $scope.tagList.remove(index);
- }
- };
- };
- }],
- link: function(scope, element, attrs, ngModelCtrl) {
- var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace, KEYS.delete, KEYS.left, KEYS.right],
- tagList = scope.tagList,
- events = scope.events,
- options = scope.options,
- input = element.find('input'),
- validationOptions = ['minTags', 'maxTags', 'allowLeftoverText'],
- setElementValidity;
-
- setElementValidity = function() {
- ngModelCtrl.$setValidity('maxTags', tagList.items.length <= options.maxTags);
- ngModelCtrl.$setValidity('minTags', tagList.items.length >= options.minTags);
- ngModelCtrl.$setValidity('leftoverText', scope.hasFocus || options.allowLeftoverText ? true : !scope.newTag.text());
- };
-
- ngModelCtrl.$isEmpty = function(value) {
- return !value || !value.length;
- };
-
- scope.newTag = {
- text: function(value) {
- if (angular.isDefined(value)) {
- scope.text = value;
- events.trigger('input-change', value);
- }
- else {
- return scope.text || '';
- }
- },
- invalid: null
- };
-
- scope.track = function(tag) {
- return tag[options.keyProperty || options.displayProperty];
- };
-
- scope.$watch('tags', function(value) {
- if (value) {
- tagList.items = tiUtil.makeObjectArray(value, options.displayProperty);
- scope.tags = tagList.items;
- }
- else {
- tagList.items = [];
- }
- });
-
- scope.$watch('tags.length', function() {
- setElementValidity();
-
- // ngModelController won't trigger validators when the model changes (because it's an array),
- // so we need to do it ourselves. Unfortunately this won't trigger any registered formatter.
- ngModelCtrl.$validate();
- });
-
- attrs.$observe('disabled', function(value) {
- scope.disabled = value;
- });
-
- scope.eventHandlers = {
- input: {
- keydown: function($event) {
- events.trigger('input-keydown', $event);
- },
- focus: function() {
- if (scope.hasFocus) {
- return;
- }
-
- scope.hasFocus = true;
- events.trigger('input-focus');
- },
- blur: function() {
- $timeout(function() {
- var activeElement = $document.prop('activeElement'),
- lostFocusToBrowserWindow = activeElement === input[0],
- lostFocusToChildElement = element[0].contains(activeElement);
-
- if (lostFocusToBrowserWindow || !lostFocusToChildElement) {
- scope.hasFocus = false;
- events.trigger('input-blur');
- }
- });
- },
- paste: function($event) {
- $event.getTextData = function() {
- var clipboardData = $event.clipboardData || ($event.originalEvent && $event.originalEvent.clipboardData);
- return clipboardData ? clipboardData.getData('text/plain') : $window.clipboardData.getData('Text');
- };
- events.trigger('input-paste', $event);
- }
- },
- host: {
- click: function() {
- if (scope.disabled) {
- return;
- }
- input[0].focus();
- }
- },
- tag: {
- click: function(tag) {
- events.trigger('tag-clicked', { $tag: tag });
- }
- }
- };
-
- events
- .on('tag-added', scope.onTagAdded)
- .on('invalid-tag', scope.onInvalidTag)
- .on('tag-removed', scope.onTagRemoved)
- .on('tag-clicked', scope.onTagClicked)
- .on('tag-added', function() {
- scope.newTag.text('');
- })
- .on('tag-added tag-removed', function() {
- scope.tags = tagList.items;
- // Ideally we should be able call $setViewValue here and let it in turn call $setDirty and $validate
- // automatically, but since the model is an array, $setViewValue does nothing and it's up to us to do it.
- // Unfortunately this won't trigger any registered $parser and there's no safe way to do it.
- ngModelCtrl.$setDirty();
- })
- .on('invalid-tag', function() {
- scope.newTag.invalid = true;
- })
- .on('option-change', function(e) {
- if (validationOptions.indexOf(e.name) !== -1) {
- setElementValidity();
- }
- })
- .on('input-change', function() {
- tagList.clearSelection();
- scope.newTag.invalid = null;
- })
- .on('input-focus', function() {
- element.triggerHandler('focus');
- ngModelCtrl.$setValidity('leftoverText', true);
- })
- .on('input-blur', function() {
- if (options.addOnBlur && !options.addFromAutocompleteOnly) {
- tagList.addText(scope.newTag.text());
- }
- element.triggerHandler('blur');
- setElementValidity();
- })
- .on('input-keydown', function(event) {
- var key = event.keyCode,
- addKeys = {},
- shouldAdd, shouldRemove, shouldSelect, shouldEditLastTag;
-
- if (tiUtil.isModifierOn(event) || hotkeys.indexOf(key) === -1) {
- return;
- }
-
- addKeys[KEYS.enter] = options.addOnEnter;
- addKeys[KEYS.comma] = options.addOnComma;
- addKeys[KEYS.space] = options.addOnSpace;
-
- shouldAdd = !options.addFromAutocompleteOnly && addKeys[key];
- shouldRemove = (key === KEYS.backspace || key === KEYS.delete) && tagList.selected;
- shouldEditLastTag = key === KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag;
- shouldSelect = (key === KEYS.backspace || key === KEYS.left || key === KEYS.right) && scope.newTag.text().length === 0 && !options.enableEditingLastTag;
-
- if (shouldAdd) {
- tagList.addText(scope.newTag.text());
- }
- else if (shouldEditLastTag) {
- var tag;
-
- tagList.selectPrior();
- tag = tagList.removeSelected();
-
- if (tag) {
- scope.newTag.text(tag[options.displayProperty]);
- }
- }
- else if (shouldRemove) {
- tagList.removeSelected();
- }
- else if (shouldSelect) {
- if (key === KEYS.left || key === KEYS.backspace) {
- tagList.selectPrior();
- }
- else if (key === KEYS.right) {
- tagList.selectNext();
- }
- }
-
- if (shouldAdd || shouldSelect || shouldRemove || shouldEditLastTag) {
- event.preventDefault();
- }
- })
- .on('input-paste', function(event) {
- if (options.addOnPaste) {
- var data = event.getTextData();
- var tags = data.split(options.pasteSplitPattern);
-
- if (tags.length > 1) {
- tags.forEach(function(tag) {
- tagList.addText(tag);
- });
- event.preventDefault();
- }
- }
- });
- }
- };
-}]);
-
-
-/**
- * @ngdoc directive
- * @name tiTagItem
- * @module ngTagsInput
- *
- * @description
- * Represents a tag item. Used internally by the tagsInput directive.
- */
-tagsInput.directive('tiTagItem', ["tiUtil", function(tiUtil) {
- return {
- restrict: 'E',
- require: '^tagsInput',
- template: '