diff --git a/app/assets/javascripts/sitewide/dropdown_selector.js b/app/assets/javascripts/sitewide/dropdown_selector.js
index 467fe8e0b..51cac3ab1 100644
--- a/app/assets/javascripts/sitewide/dropdown_selector.js
+++ b/app/assets/javascripts/sitewide/dropdown_selector.js
@@ -100,7 +100,7 @@ var dropdownSelector = (function() {
// Save data to the field
function updateCurrentData(container, data) {
- container.find('.data-field').val(JSON.stringify(data));
+ container.find('.data-field').val(JSON.stringify(data)).change();
}
// Search filter for non-ajax data
@@ -293,6 +293,7 @@ var dropdownSelector = (function() {
var optionContainer;
var perfectScroll;
var dropdownContainer;
+ var toggleElement;
if (selectElement.length === 0) return;
@@ -316,6 +317,30 @@ var dropdownSelector = (function() {
`).appendTo(dropdownContainer);
+ // Blank option
+ if (selectElement.data('blank')) {
+ $(`
${selectElement.data('blank')}
`)
+ .appendTo(dropdownContainer.find('.dropdown-container'))
+ .click(() => {
+ dropdownContainer.find('.dropdown-group, .dropdown-option').removeClass('select');
+ saveData(selectElement, dropdownContainer);
+ dropdownContainer.removeClass('open');
+ });
+ }
+
+ if (selectElement.data('toggle-target')) {
+ dropdownContainer.find('.data-field').on('change', function() {
+ toggleElement = $(selectElement.data('toggle-target'));
+ if (getCurrentData(dropdownContainer).length > 0) {
+ toggleElement.removeClass('hidden');
+ toggleElement.find('input, select').removeAttr('disabled');
+ } else {
+ toggleElement.addClass('hidden');
+ toggleElement.find('input, select').attr('disabled', true);
+ }
+ });
+ }
+
// If we setup Select All we draw it and add correspond logic
if (selectElement.data('select-all-button')) {
$(`${selectElement.data('select-all-button')}
`)
diff --git a/app/assets/javascripts/users/settings/teams/invite_users_modal.js b/app/assets/javascripts/users/settings/teams/invite_users_modal.js
index 1c2de5065..f89a5deff 100644
--- a/app/assets/javascripts/users/settings/teams/invite_users_modal.js
+++ b/app/assets/javascripts/users/settings/teams/invite_users_modal.js
@@ -26,6 +26,8 @@
var teamSelectorDropdown = modal.find('[data-role=team-selector-dropdown]');
var teamSelectorDropdown2 = $();
var emailsInput = modal.find('.emails-input');
+ var teamsInput = modal.find('.teams-input');
+ var roleInput = modal.find('.role-input');
var recaptchaErrorMsgDiv = modal.find('#recaptcha-error-msg');
var recaptchaErrorText = modal.find('#recaptcha-error-msg>span');
@@ -46,7 +48,7 @@
labelHTML: true,
tagClass: 'users-dropdown-list',
inputTagMode: true,
- customDropdownIcon: () => { return ' '; },
+ customDropdownIcon: () => { return ' '; },
onChange: () => {
let values = dropdownSelector.getValues(emailsInput);
if (values.length > 0) {
@@ -114,6 +116,8 @@
modal.find('[data-action=invite]').off('click').on('click', function() {
var data = {
emails: dropdownSelector.getValues(emailsInput),
+ team_ids: dropdownSelector.getValues(teamsInput),
+ role: roleInput.val(),
'g-recaptcha-response': $('#recaptcha-invite-modal').val()
};
@@ -121,24 +125,24 @@
switch (type) {
case 'invite_to_team':
- data.teamId = modal.attr('data-team-id');
+ data.team_ids = [modal.attr('data-team-id')];
data.role = $(this).attr('data-team-role');
break;
case 'invite_to_team_with_role':
- data.teamId = modal.attr('data-team-id');
+ data.team_ids = [modal.attr('data-team-id')];
data.role = modal.attr('data-team-role');
break;
case 'invite':
break;
case 'invite_with_team_selector':
if (teamSelectorCheckbox.is(':checked')) {
- data.teamId = teamSelectorDropdown.val();
+ data.team_ids = [teamSelectorDropdown.val()];
data.role = $(this).attr('data-team-role');
}
break;
case 'invite_with_team_selector_and_role':
if (teamSelectorCheckbox.is(':checked')) {
- data.teamId = teamSelectorDropdown.val();
+ data.team_ids = [teamSelectorDropdown.val()];
data.role = modal.attr('data-team-role');
}
break;
@@ -181,6 +185,11 @@
script.src = 'https://www.google.com/recaptcha/api.js?hl=en';
$(script).insertAfter('#recaptcha-service');
// Remove 'data-invited="true"' status
+
+ dropdownSelector.init(teamsInput, {
+ optionClass: 'checkbox-icon'
+ });
+
modal.removeAttr('data-invited');
}).on('hide.bs.modal', function() {
// 'Reset' modal state
diff --git a/app/assets/stylesheets/shared/dropdown_selector.scss b/app/assets/stylesheets/shared/dropdown_selector.scss
index f85df08d9..060a3805e 100644
--- a/app/assets/stylesheets/shared/dropdown_selector.scss
+++ b/app/assets/stylesheets/shared/dropdown_selector.scss
@@ -3,6 +3,19 @@
@import "constants";
+.select-container--with-search .dropdown-selector-container {
+ &.active,
+ &.open {
+ .right-icon.fa-search {
+ display: block;
+ }
+
+ .right-icon.fa-caret-down {
+ display: none;
+ }
+ }
+}
+
.dropdown-selector-container {
display: inline-block;
float: left;
@@ -163,6 +176,18 @@
z-index: 5;
}
+ .dropdown-blank {
+ border-radius: 0;
+ padding-left: 7px;
+ text-align: left;
+ width: 100%;
+
+ &:hover {
+ background: $brand-primary;
+ color: $color-white;
+ }
+ }
+
.dropdown-option {
align-items: center;
cursor: pointer;
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index 0049e6adc..ab5d80afc 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -1319,6 +1319,7 @@ body > .loading-overlay {
.modal-invite-users {
.bootstrap-tagsinput {
min-width: 450px;
+ width: 100%;
}
.results-container .alert {
@@ -1342,6 +1343,10 @@ body > .loading-overlay {
}
}
+ .dropdown-selector-container {
+ margin-bottom: 20px;
+ }
+
.g-recaptcha {
margin-top: 20px;
}
@@ -1486,3 +1491,15 @@ a.disabled-with-click-events {
}
}
+
+.form-select {
+ border: 1px solid $color-silver-chalice;
+ border-radius: 4px;
+ color: $color-emperor;
+ display: block;
+ font-size: 14px;
+ min-height: 36px;
+ outline: 0;
+ padding: 8px 42px 3px 3px;
+ width: 100%;
+}
diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb
index 44042ac48..af3b16aba 100644
--- a/app/controllers/users/invitations_controller.rb
+++ b/app/controllers/users/invitations_controller.rb
@@ -100,46 +100,49 @@ module Users
user.delay.deliver_invitation
end
- if @team && user
- user_team = UserTeam.find_by_user_id_and_team_id(user.id, @team.id)
- if user_team
- result[:status] = :user_exists_and_in_team
- else
- # Also generate user team relation
- user_team = UserTeam.new(
- user: user,
- team: @team,
- role: @role
- )
- user_team.save
+ if @teams.any? && user
+ @teams.each do |team|
+ user_team = UserTeam.find_by(user_id: user.id, team_id: team.id)
+ if user_team
+ result[:status] = :user_exists_and_in_team
+ else
+ # Also generate user team relation
+ user_team = UserTeam.new(
+ user: user,
+ team: team,
+ role: @role || 'normal_user'
+ )
+ user_team.save
- generate_notification(
- @user,
- user,
- user_team.team,
- user_team.role_str
- )
- Activities::CreateActivityService
- .call(activity_type: :invite_user_to_team,
- owner: current_user,
- subject: @team,
- team: @team,
- message_items: {
- team: @team.id,
- user_invited: user.id,
- role: user_team.role_str
- })
+ generate_notification(
+ @user,
+ user,
+ user_team.team,
+ user_team.role_str
+ )
- result[:status] = if result[:status] == :user_exists && !user.confirmed?
- :user_exists_unconfirmed_invited_to_team
- elsif result[:status] == :user_exists
- :user_exists_invited_to_team
- else
- :user_created_invited_to_team
- end
+ Activities::CreateActivityService
+ .call(activity_type: :invite_user_to_team,
+ owner: current_user,
+ subject: team,
+ team: team,
+ message_items: {
+ team: team.id,
+ user_invited: user.id,
+ role: user_team.role_str
+ })
+
+ result[:status] = if result[:status] == :user_exists && !user.confirmed?
+ :user_exists_unconfirmed_invited_to_team
+ elsif result[:status] == :user_exists
+ :user_exists_invited_to_team
+ else
+ :user_created_invited_to_team
+ end
+ end
+
+ result[:user_teams] << user_team
end
-
- result[:user_team] = user_team
end
@invite_results << result
@@ -156,6 +159,17 @@ module Users
end
end
+ def invitable_teams
+ teams = current_user.teams
+ .select(:id, :name)
+ .joins(:user_teams)
+ .where('user_teams.role': UserTeam.roles[:admin])
+ .distinct
+ .select { |team| can_invite_team_users?(team) }
+
+ render json: teams.map { |t| { value: t.id, label: t.name } }.to_json
+ end
+
private
def update_sanitized_params
@@ -185,12 +199,13 @@ module Users
def check_invite_users_permission
@user = current_user
@emails = params[:emails]&.map(&:downcase)
- @team = Team.find_by_id(params['teamId'])
+
+ @teams = Team.where(id: params[:team_ids]).select { |team| can_manage_team_users?(team) }
+ return render_403 if @teams.none?
+
@role = params['role']
- return render_403 if @team && @role.nil? # if we select team, we must select role
return render_403 if @emails.blank? # We must have at least one email
- return render_403 if @team && !can_manage_team_users?(@team) # if we select team, we must check permission
return render_403 if @role && !UserTeam.roles.key?(@role) # if we select role, we must check that this role exist
end
end
diff --git a/app/views/shared/_invite_users_modal.html.erb b/app/views/shared/_invite_users_modal.html.erb
index e4a77c6df..c92171852 100644
--- a/app/views/shared/_invite_users_modal.html.erb
+++ b/app/views/shared/_invite_users_modal.html.erb
@@ -74,29 +74,38 @@ invite_to_team = type.in?(%w(invite_to_team invite_to_team_with_role))
-
- <%= t('invite_users.input_subtitle') %>
+
<%= t('invite_users.input_subtitle') %>
<% if current_user && type.in?(['invite_with_team_selector', 'invite_with_team_selector_and_role']) %>
<% # Only allow inviting to teams where user is admin %>
- <% teams = current_user.teams
- .joins(:user_teams)
- .where('user_teams.role': UserTeam.roles[:admin])
- .select { |team| can_invite_team_users?(team) } %>
- <% if teams.any? %>
-
-
-
-
<%= t('invite_users.invite_to_team_heading') %>
+
+
+ <%= t('invite_users.select_team') %>
+
+
+
+
+
+
+
+
+ <%= t('invite_users.select_team_role') %>
+
+ <%= select_tag "role",
+ options_for_select(UserTeam.roles.keys
+ .map { |k| [k.humanize + (k == 'normal_user' ? " (#{t('invite_users.default')})" : ''), k] }, 'normal_user'),
+ disabled: true, class: "role-input from-control form-select"
+ %>
- <%= select_tag('team-select',
- options_for_select(teams.pluck(:name, :id)),
- { class: 'form-control selectpicker',
- 'data-role' => 'team-selector-dropdown',
- disabled: 'disabled' }) %>
- <% end %>
+
<% end %>
<% if ENV['ENABLE_RECAPTCHA'] == 'true' %>