Skip to content

Commit

Permalink
Popovers for user context now are included wherever an activity strea…
Browse files Browse the repository at this point in the history
…m has a gravatar
  • Loading branch information
johnmartin committed Oct 2, 2012
1 parent e02ff1f commit f8851b2
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 14 deletions.
17 changes: 11 additions & 6 deletions ckan/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ def linked_user(user, maxlength=0, avatar=20):
return user_name
if user:
name = user.name if model.User.VALID_NAME.match(user.name) else user.id
icon = gravatar(user.email_hash, avatar)
icon = gravatar(user.email_hash, avatar, None, user.id)
displayname = user.display_name
if maxlength and len(user.display_name) > maxlength:
displayname = displayname[:maxlength] + '...'
Expand Down Expand Up @@ -679,28 +679,33 @@ def dict_list_reduce(list_, key, unique=True):
return new_list


def linked_gravatar(email_hash, size=100, default=None):
def linked_gravatar(email_hash, size=100, default=None, user_id=None):
return literal(
'<a href="https://gravatar.com/" target="_blank" ' +
'title="%s">' % _('Update your avatar at gravatar.com') +
'%s</a>' % gravatar(email_hash, size, default)
'%s</a>' % gravatar(email_hash, size, default, user_id)
)

_VALID_GRAVATAR_DEFAULTS = ['404', 'mm', 'identicon', 'monsterid',
'wavatar', 'retro']


def gravatar(email_hash, size=100, default=None):
def gravatar(email_hash, size=100, default=None, user_id=None):
if default is None:
default = config.get('ckan.gravatar_default', 'identicon')

if not default in _VALID_GRAVATAR_DEFAULTS:
# treat the default as a url
default = urllib.quote(default, safe='')

if user_id is None:
user_id = ""

url = url_for(controller='user', action='read', id=user_id)

return literal('''<img src="http://gravatar.com/avatar/%s?s=%d&amp;d=%s"
class="gravatar" width="%s" height="%s" />'''
% (email_hash, size, default, size, size)
class="gravatar" width="%s" height="%s" data-module="user-context" data-module-id="%s" data-module-url="%s" />'''
% (email_hash, size, default, size, size, user_id, url)
)

def pager_url(page, partial=None, **kwargs):
Expand Down
16 changes: 11 additions & 5 deletions ckan/public/base/javascript/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,23 @@
return path;
},

action: function(path, data, fn) {
call: function(type, path, data, fn) {
var url = this.url('/api/action/' + path);
jQuery.ajax({
var options = {
contentType: 'application/json',
url: url,
data: data,
dataType: 'json',
processData: false,
type: 'POST',
success: fn
});
};
if (type == 'POST') {
options.type = 'POST';
options.data = JSON.stringify(data);
} else {
options.type = 'GET';
options.url += data;
}
jQuery.ajax(options);
},

/* Requests a block of HTML from the snippet API endpoint. Optional
Expand Down
3 changes: 1 addition & 2 deletions ckan/public/base/javascript/modules/follow.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ this.ckan.module('follow', function($, _) {
) {
var client = this.sandbox.client;
var path = options.action + '_' + options.type;
var data = JSON.stringify({ id : options.id });
options.loading = true;
this.el.addClass('disabled');
client.action(path, data, this._onClickLoaded);
client.call('POST', path, { id : options.id }, this._onClickLoaded);
}
return false;
},
Expand Down
114 changes: 114 additions & 0 deletions ckan/public/base/javascript/modules/user-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
window.user_context_dict = {};
this.ckan.module('user-context', function($, _) {
return {
options : {
id: null,
loading: false,
authed: false,
url: '',
template: '<div class="profile-info">{{ about }}<div class="btn-group">{{ buttons }}</div><div class="nums"><dl><dt>Followers</dt><dd>{{ followers }}</dd></dl><dl><dt>Datasets</dt><dd>{{ datasets }}</dd></dl><dl><dt>Edits</dt><dd>{{ edits }}</dd></dl></div></div>',
i18n: {
follow: _('Follow'),
unfollow: _('Unfollow'),
loading: _('Loading...')
}
},
initialize: function () {
if (
this.options.id != true
&& this.options.id != null
) {
$.proxyAll(this, /_on/);
if ($('.account').hasClass('authed')) {
this.options.authed = $('.account').data('me');
}
this.el.popover({
animation: false,
content: this.i18n('loading'),
placement: 'bottom'
});
this.el.on('mouseover', this._onMouseOver);
}
},
getUserData: function() {
if (!this.loading) {
var id = this.options.id;
if (typeof window.user_context_dict[id] == 'undefined') {
var client = this.sandbox.client;
this.loading = true;
client.call('GET', 'user_show', '?id=' + id, this._onHandleUserData);
} else {
this._onHandleUserData(window.user_context_dict[id]);
}
}
},
_onMouseOver: function(e) {
$('[data-module="user-context"]').popover('hide');
this.el.popover('show');
this.getUserData();
},
_onHandleUserData: function(json) {
this.loading = false;
if (json.success) {
var id = this.options.id;
var client = this.sandbox.client;
var user = json.result;
var popover = this.el.data('popover');
if (typeof user.number_of_followers == 'undefined') {
user.number_of_followers = '...';
client.call('GET', 'user_follower_count', '?id=' + id, this._onHandleUserFollowersData);
}
if (typeof user.am_following_user == 'undefined') {
user.am_following_user = 'disabled';
client.call('GET', 'am_following_user', '?id=' + id, this._onHandleAmFollowingData);
}
if (typeof popover.$tip != 'undefined') {
var tip = popover.$tip;
var about = user.about ? '<p class="about">' + user.about + '</p>' : '';
var template = this.options.template
.replace('{{ about }}', about)
.replace('{{ followers }}', user.number_of_followers)
.replace('{{ datasets }}', user.number_administered_packages)
.replace('{{ edits }}', user.number_of_edits)
.replace('{{ buttons }}', this._getButtons(user));
$('.popover-title', tip).html('<a href="javascript:;" class="popover-close">&times;</a>' + user.display_name);
$('.popover-content', tip).html(template);
$('.popover-close', tip).on('click', this._onHandlePopoverClose);
}
window.user_context_dict[this.options.id] = json;
}
},
_onHandlePopoverClose: function() {
this.el.popover('hide');
},
_onHandleUserFollowersData: function(json) {
var data = window.user_context_dict[this.options.id];
data.result.number_of_followers = json.result;
this._onHandleUserData(data);
},
_onHandleAmFollowingData: function(json) {
var data = window.user_context_dict[this.options.id];
data.result.am_following_user = json.result;
this._onHandleUserData(data);
},
_getButtons: function(user) {
var html = '';
if (
this.options.authed
&& user.id != this.options.authed
) {
if (user.am_following_user) {
if (user.am_following_user == 'disabled') {
html = '<a href="javascript:;" class="btn disabled">' + this.i18n('loading') + '</a>';
} else {
html = '<a href="javascript:;" class="btn btn-danger"><i class="icon-remove-sign"></i> ' + this.i18n('unfollow') + '</a>';
}
} else {
html = '<a href="javascript:;" class="btn btn-success"><i class="icon-plus-sign"></i> ' + this.i18n('follow') + '</a>';
}
}
html += '<a href="' + this.options.url + '" class="btn"><i class="icon-user"></i> View profile</a>';
return html;
}
};
});
1 change: 1 addition & 0 deletions ckan/public/base/javascript/resource.config
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ main =
modules/custom-fields.js
modules/related-item.js
modules/follow.js
modules/user-context.js
11 changes: 11 additions & 0 deletions ckan/public/base/less/activity.less
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@
}
}

// For profile information that appears in the popover
.popover {
.about {
margin-bottom: 10px;
}
.popover-close {
float: right;
text-decoration: none;
}
}

// colors
.activity .item {
&.added-tag i { background-color: spin(@activityColorNew, 60); }
Expand Down
103 changes: 103 additions & 0 deletions ckan/public/base/vendor/bootstrap/js/bootstrap-popover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* ===========================================================
* bootstrap-popover.js v2.1.1
* http://twitter.github.com/bootstrap/javascript.html#popovers
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================================================== */


!function ($) {

"use strict"; // jshint ;_;


/* POPOVER PUBLIC CLASS DEFINITION
* =============================== */

var Popover = function (element, options) {
this.init('popover', element, options)
}


/* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
========================================== */

Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {

constructor: Popover

, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
, content = this.getContent()

$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
$tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)

$tip.removeClass('fade top bottom left right in')
}

, hasContent: function () {
return this.getTitle() || this.getContent()
}

, getContent: function () {
var content
, $e = this.$element
, o = this.options

content = $e.attr('data-content')
|| (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)

return content
}

, tip: function () {
if (!this.$tip) {
this.$tip = $(this.options.template)
}
return this.$tip
}

, destroy: function () {
this.hide().$element.off('.' + this.type).removeData(this.type)
}

})


/* POPOVER PLUGIN DEFINITION
* ======================= */

$.fn.popover = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('popover')
, options = typeof option == 'object' && option
if (!data) $this.data('popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}

$.fn.popover.Constructor = Popover

$.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
placement: 'right'
, trigger: 'click'
, content: ''
, template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
})

}(window.jQuery);

0 comments on commit f8851b2

Please sign in to comment.