yeehaw add collaborators

This commit is contained in:
Connor Turland 2016-04-21 18:54:26 -04:00
parent fa51ee850e
commit 6c7ce56e4f
18 changed files with 277 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

View file

@ -434,6 +434,8 @@ Metamaps.Map.InfoBox = {
self.attachEventListeners()
self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html())
},
toggleBox: function (event) {
@ -474,7 +476,7 @@ Metamaps.Map.InfoBox = {
var map = Metamaps.Active.Map
var obj = map.pick('permission', 'contributor_count', 'topic_count', 'synapse_count')
var obj = map.pick('permission', 'topic_count', 'synapse_count')
var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper)
var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper)
@ -485,6 +487,7 @@ Metamaps.Map.InfoBox = {
obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc')
obj['map_creator_tip'] = isCreator ? self.changePermissionText : ''
obj['contributor_count'] = relevantPeople.length
obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : ''
obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : ''
obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : "<%= asset_path('user.png') %>"
@ -558,6 +561,85 @@ Metamaps.Map.InfoBox = {
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () {
$('.mapContributors .tip').hide()
})
self.addTypeahead()
},
addTypeahead: function () {
var self = Metamaps.Map.InfoBox
// for autocomplete
var collaborators = {
name: 'collaborators',
limit: 9999,
display: function(s) { return s.label; },
templates: {
notFound: function(s) {
return Hogan.compile($('#collaboratorSearchTemplate').html()).render({
value: "No results",
label: "No results",
rtype: "noresult",
profile: "<%= asset_path('user.png') %>",
});
},
suggestion: function(s) {
return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s);
},
},
source: new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/search/mappers?term=%QUERY',
wildcard: '%QUERY',
},
})
}
// for adding map collaborators, who will have edit rights
if (Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')) {
$('.collaboratorSearchField').typeahead(
{
highlight: false,
},
[collaborators]
)
$('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick)
$('.mapContributors .removeCollaborator').click(function () {
self.removeCollaborator(parseInt($(this).data('id')))
})
}
},
removeCollaborator: function (collaboratorId) {
var self = Metamaps.Map.InfoBox
Metamaps.Collaborators.remove(Metamaps.Collaborators.get(collaboratorId))
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
self.updateNumbers()
},
addCollaborator: function (newCollaboratorId) {
var self = Metamaps.Map.InfoBox
if (Metamaps.Collaborators.get(newCollaboratorId)) {
Metamaps.GlobalUI.notifyUser('That user already has access')
return
}
function callback(mapper) {
Metamaps.Collaborators.add(mapper)
var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id })
$.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds })
var name = Metamaps.Collaborators.get(newCollaboratorId).get('name')
Metamaps.GlobalUI.notifyUser(name + ' will be notified by email')
self.updateNumbers()
}
$.getJSON('/users/' + newCollaboratorId + '.json', callback)
},
handleResultClick: function (event, item) {
var self = Metamaps.Map.InfoBox
self.addCollaborator(item.id)
$('.collaboratorSearchField').typeahead('val', '')
},
updateNameDescPerm: function (name, desc, perm) {
$('.mapInfoName .best_in_place_name').html(name)
@ -567,14 +649,24 @@ Metamaps.Map.InfoBox = {
createContributorList: function () {
var self = Metamaps.Map.InfoBox
var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
var activeMapperIsCreator = Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')
var string = ''
string += '<ul>'
relevantPeople.each(function (m) {
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name') + '</a></li>'
var isCreator = Metamaps.Active.Map.get('user_id') === m.get('id')
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name')
if (isCreator) string += ' (creator)'
string += '</a>'
if (activeMapperIsCreator && !isCreator) string += '<span class="removeCollaborator" data-id="' + m.get('id') + '"></span>'
string += '</li>'
})
string += '</ul>'
if (activeMapperIsCreator) {
string += '<div class="collabSearchField"><span class="addCollab"></span><input class="collaboratorSearchField" placeholder="Add a collaborator!"></input></div>'
}
return string
},
updateNumbers: function () {
@ -594,6 +686,10 @@ Metamaps.Map.InfoBox = {
$('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class)
$('.mapContributors span').text(relevantPeople.length)
$('.mapContributors .tip').html(self.createContributorList())
self.addTypeahead()
$('.mapContributors .tip').unbind().click(function (event) {
event.stopPropagation()
})
$('.mapTopics').text(Metamaps.Topics.length)
$('.mapSynapses').text(Metamaps.Synapses.length)

View file

@ -1569,6 +1569,11 @@ h3.filterBox {
background-repeat: no-repeat;
text-align: left;
}
.commonsMap .mapContributors {
visibility: hidden;
}
.mapContributors {
position: relative;
height: 30px;
@ -1576,6 +1581,7 @@ h3.filterBox {
padding: 0;
width: 64px;
}
#mapContribs {
float: left;
border: 2px solid #424242;
@ -1591,7 +1597,7 @@ h3.filterBox {
#mapContribs.multiple {
box-shadow: 1px 1px 0 0 #B5B5B5,3px 2px 0 0 #424242,4px 3px 0 0 #B5B5B5,5px 4px 0 0 #424242;
}
.mapContributors span {
.mapContributors span.count {
height: 20px;
padding-top: 5px;
padding-left: 8px;
@ -1626,6 +1632,7 @@ h3.filterBox {
.mapContributors .tip {
top: 45px;
left: -10px;
min-width: 200px;
}
.mapContributors .tip ul {
@ -1652,7 +1659,7 @@ h3.filterBox {
.mapContributors .tip li a {
color: white;
}
.mapContributors div:after {
.mapContributors div.tip:after {
content: '';
position: absolute;
top: -4px;
@ -1672,6 +1679,90 @@ h3.filterBox {
border-radius: 14px;
}
.mapContributors span.twitter-typeahead {
padding: 0;
}
.collabSearchField {
text-align: left;
}
.collabNameWrapper {
float: left;
}
.collabIconWrapper img.icon {
position: relative;
top: 0;
left: 0;
}
.collabIconWrapper {
position: relative;
float: left;
padding: 0 4px;
}
.mapContributors .collabName {
font-weight: normal;
font-size: 14px;
line-height: 28px;
color: #424242;
padding: 0 4px;
}
span.removeCollaborator {
position: absolute;
top: 11px;
right: 8px;
height: 16px;
width: 16px;
background-image: url(<%= asset_data_uri('removecollab_sprite.png') %>);
cursor: pointer;
}
span.removeCollaborator:hover {
background-position: -16px 0;
}
span.addCollab {
width: 16px;
height: 16px;
background-image: url(<%= asset_data_uri('addcollab_sprite.png') %>);
display: inline-block;
vertical-align: middle;
margin: 0 12px 0 10px;
}
input.collaboratorSearchField {
background: #FFFFFF;
height: 14px;
margin: 0;
padding: 10px 6px;
border: none;
border-radius: 2px;
outline: none;
font-size: 14px;
line-height: 14px;
color: #424242;
font-family: 'din-medium', helvetica, sans-serif;
}
.tt-dataset.tt-dataset-collaborators {
padding: 2px;
background: #E0E0E0;
min-width: 156px;
border-radius: 2px;
}
.tt-dataset.tt-dataset .collabResult {
padding: 4px;
}
.collabResult.tt-suggestion.tt-cursor, .collabResult.tt-suggestion:hover {
background-color: #CCCCCC;
}
.mapInfoBox .mapPermission .tooltips {
top: -20px;
right: 36px;

View file

@ -4,7 +4,7 @@ class MainController < ApplicationController
include UsersHelper
include SynapsesHelper
after_action :verify_policy_scoped, except: :requestinvite
after_action :verify_policy_scoped, except: [:requestinvite, :searchmappers]
respond_to :html, :json
@ -133,8 +133,7 @@ class MainController < ApplicationController
#remove "mapper:" if appended at beginning
term = term[7..-1] if term.downcase[0..6] == "mapper:"
search = term.downcase + '%'
builder = policy_scope(User) # TODO do I need to policy scope? I guess yes to verify_policy_scoped
builder = builder.where('LOWER("name") like ?', search)
builder = User.where('LOWER("name") like ?', search)
@mappers = builder.order(:name)
else
@mappers = []

View file

@ -241,10 +241,11 @@ class MapsController < ApplicationController
not @map.collaborators.include?(user)
end
}
removed = @map.collaborators.select { |user| not userIds.include?(user.id) }.map(&:id)
removed = @map.collaborators.select { |user| not userIds.include?(user.id.to_s) }.map(&:id)
added.each { |uid|
um = UserMap.create({ user_id: uid.to_i, map_id: @map.id })
# send email here
user = User.find(uid.to_i)
MapMailer.invite_to_edit_email(@map, current_user, user).deliver_later
}
removed.each { |uid|
@map.user_maps.select{ |um| um.user_id == uid }.each{ |um| um.destroy }

View file

@ -0,0 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: "team@metamaps.cc"
layout 'mailer'
end

11
app/mailers/map_mailer.rb Normal file
View file

@ -0,0 +1,11 @@
class MapMailer < ApplicationMailer
default from: "team@metamaps.cc"
def invite_to_edit_email(map, inviter, invitee)
@inviter = inviter
@map = map
@url = map_url(@map)
subject = @map.name + ' - Invitation to edit'
mail(to: invitee.email, subject: subject)
end
end

View file

@ -12,7 +12,7 @@
<div class="infoStatIcon mapContributors hoverForTip">
<img id="mapContribs" class="{{contributors_class}}"
width="25" height="25" src="{{contributor_image}}" />
<span>{{contributor_count}}</span>
<span class="count">{{contributor_count}}</span>
<div class="tip">{{{contributor_list}}}</div>
</div>
<div class="infoStatIcon mapTopics">
@ -181,6 +181,18 @@
</div>
</script>
<script type="text/template" id="collaboratorSearchTemplate">
<div class="collabResult">
<div class="collabIconWrapper">
<img class="icon" width="25" height="25" src="{{profile}}">
</div>
<div class="collabNameWrapper">
<p class="collabName">{{label}}</p>
</div>
<div class="clearfloat"></div>
</div>
</script>
<script type="text/template" id="synapseAutocompleteTemplate">
<div class="result{{rtype}}">
<p class="autocompleteSection synapseDesc">{{label}}</p>

View file

@ -0,0 +1,5 @@
<html>
<body>
<%= yield %>
</body>
</html>

View file

@ -0,0 +1 @@
<%= yield %>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<p><%= @inviter.name %> has invited you to <span style="font-weight: bold">collaboratively edit</span> the following map on Metamaps:</p>
<p style="font-weight: bold;"><%= link_to @map.name, @url %></p>
<p style="font-size: 11px;">Make sense with Metamaps</p>
</body>
</html>

View file

@ -0,0 +1,7 @@
<%= @inviter.name %> has invited you to collaboratively edit the following map on Metamaps:
<%= @map.name + ' [' + @url + ']' %>
Make sense with Metamaps

View file

@ -22,12 +22,24 @@
<% elsif relevantPeople.count > 2 %>
<img id="mapContribs" width="25" height="25" src="<%= relevantPeople[0].image.url(:thirtytwo) %>" class="multiple" />
<% end %>
<span><%= relevantPeople.count %></span>
<div class="tip"> <ul><% relevantPeople.each_with_index do |c, index| %>
<li ><a href="/explore/mapper/<%= c.id %>" > <img class="rtUserImage" width="25" height="25" src="<%= asset_path c.image.url(:thirtytwo) %>" />
<%= c.name %></a>
<span class="count"><%= relevantPeople.count %></span>
<div class="tip">
<ul><% relevantPeople.each_with_index do |c, index| %>
<li>
<a href="/explore/mapper/<%= c.id %>" > <img class="rtUserImage" width="25" height="25" src="<%= asset_path c.image.url(:thirtytwo) %>" />
<%= c.name %>
<% if @map.user == c %> (creator)<% end %>
</a>
<% if @map.user != c && @map.user == current_user %>
<span class="removeCollaborator" data-id=" + m.get('id') + "></span>
<% end %>
</li>
<% end %></ul></div>
<% end %>
</ul>
<% if @map.user == current_user %>
<div class="collabSearchField"><span class="addCollab"></span><input class="collaboratorSearchField" placeholder="Add a collaborator!"></input></div>
<% end %>
</div>
</div>
<div class="infoStatIcon mapTopics">
<%= @map.topics.count %>

View file

@ -30,7 +30,7 @@ Metamaps::Application.configure do
port: ENV['SMTP_PORT'],
user_name: ENV['SMTP_USERNAME'],
password: ENV['SMTP_PASSWORD'],
#domain: ENV['SMTP_DOMAIN']
domain: ENV['SMTP_DOMAIN'],
authentication: 'plain',
enable_starttls_auto: true,
openssl_verify_mode: 'none' }

View file

@ -0,0 +1,5 @@
require "rails_helper"
RSpec.describe MapMailer, type: :mailer do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,4 @@
# Preview all emails at http://localhost:3000/rails/mailers/map_mailer
class MapMailerPreview < ActionMailer::Preview
end