Pop up with user information when clicking avatar on topic page

This commit is contained in:
Robin Ward 2013-10-03 12:51:30 -04:00
parent 578ef2098b
commit fc00269b7f
13 changed files with 198 additions and 20 deletions

View File

@ -105,19 +105,20 @@ Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
$('#main').on('click.discourse', 'a', function(e) { $('#main').on('click.discourse', 'a', function(e) {
if (e.isDefaultPrevented() || e.shiftKey || e.metaKey || e.ctrlKey) { return; } if (e.isDefaultPrevented() || e.shiftKey || e.metaKey || e.ctrlKey) { return; }
var $currentTarget = $(e.currentTarget); var $currentTarget = $(e.currentTarget),
var href = $currentTarget.attr('href'); href = $currentTarget.attr('href');
if (!href) { return; }
if (href === '#') { return; }
if ($currentTarget.attr('target')) { return; }
if ($currentTarget.data('auto-route')) { return; }
// If it's an ember #link-to skip it if (!href ||
if ($currentTarget.hasClass('ember-view')) { return; } href === '#' ||
$currentTarget.attr('target') ||
if ($currentTarget.hasClass('lightbox')) { return; } $currentTarget.data('ember-action') ||
if (href.indexOf("mailto:") === 0) { return; } $currentTarget.data('auto-route') ||
if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) { return; } $currentTarget.hasClass('ember-view') ||
$currentTarget.hasClass('lightbox') ||
href.indexOf("mailto:") === 0 ||
(href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i")))) {
return;
}
e.preventDefault(); e.preventDefault();
Discourse.URL.routeTo(href); Discourse.URL.routeTo(href);

View File

@ -0,0 +1,30 @@
/**
A controller for expanding information about a poster.
@class PosterExpansion
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PosterExpansionController = Discourse.ObjectController.extend({
needs: ['topic'],
show: function(user, post) {
this.setProperties({model: user, post: post});
},
close: function() {
this.set('model', null);
},
actions: {
togglePosts: function(user) {
var postStream = this.get('controllers.topic.postStream');
postStream.toggleParticipant(user.get('username'));
this.close();
}
}
});

View File

@ -133,7 +133,6 @@ Discourse.PostStream = Em.Object.extend({
hasNoFilters: Em.computed.empty('filterDesc'), hasNoFilters: Em.computed.empty('filterDesc'),
/** /**
Returns the window of posts above the current set in the stream, bound to the top of the stream. Returns the window of posts above the current set in the stream, bound to the top of the stream.
This is the collection we'll ask for when scrolling upwards. This is the collection we'll ask for when scrolling upwards.

View File

@ -259,7 +259,6 @@ Discourse.User = Discourse.Model.extend({
json.user.invited_by = Discourse.User.create(json.user.invited_by); json.user.invited_by = Discourse.User.create(json.user.invited_by);
} }
user.setProperties(json.user); user.setProperties(json.user);
return user; return user;
}); });
@ -297,6 +296,17 @@ Discourse.User = Discourse.Model.extend({
Discourse.User.reopenClass(Discourse.Singleton, { Discourse.User.reopenClass(Discourse.Singleton, {
/**
Find a `Discourse.User` for a given username.
@method findByUsername
@returns {Promise} a promise that resolves to a `Discourse.User`
**/
findByUsername: function(username) {
var user = Discourse.User.create({username: username});
return user.findDetails();
},
/** /**
The current singleton will retrieve its attributes from the `PreloadStore` The current singleton will retrieve its attributes from the `PreloadStore`
if it exists. Otherwise, no instance is created. if it exists. Otherwise, no instance is created.
@ -325,7 +335,6 @@ Discourse.User.reopenClass(Discourse.Singleton, {
}); });
}, },
/** /**
Checks if given username is valid for this email address Checks if given username is valid for this email address

View File

@ -13,6 +13,14 @@ Discourse.TopicRoute = Discourse.Route.extend({
actions: { actions: {
// Modals that can pop up within a topic // Modals that can pop up within a topic
showPosterExpansion: function(post) {
var self = this;
Discourse.User.findByUsername(post.get('username')).then(function (user) {
self.controllerFor('posterExpansion').show(user, post);
});
},
showFlags: function(post) { showFlags: function(post) {
Discourse.Route.showModal(this, 'flag', post); Discourse.Route.showModal(this, 'flag', post);
this.controllerFor('flag').setProperties({ selected: null }); this.controllerFor('flag').setProperties({ selected: null });
@ -80,9 +88,10 @@ Discourse.TopicRoute = Discourse.Route.extend({
// Clear the search context // Clear the search context
this.controllerFor('search').set('searchContext', null); this.controllerFor('search').set('searchContext', null);
this.controllerFor('posterExpansion').set('model', null);
var topicController = this.controllerFor('topic'); var topicController = this.controllerFor('topic'),
var postStream = topicController.get('postStream'); postStream = topicController.get('postStream');
postStream.cancelFilter(); postStream.cancelFilter();
topicController.set('multiSelect', false); topicController.set('multiSelect', false);

View File

@ -19,9 +19,9 @@
<div class='topic-meta-data span2'> <div class='topic-meta-data span2'>
{{#unless userDeleted}} {{#unless userDeleted}}
<div {{bindAttr class=":contents byTopicCreator:topic-creator"}}> <div {{bindAttr class=":contents byTopicCreator:topic-creator"}}>
<a href='{{unbound usernameUrl}}'>{{avatar this imageSize="large"}}</a> <a href='{{unbound usernameUrl}}' {{action showPosterExpansion this}}>{{avatar this imageSize="large"}}</a>
<h3 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h3> <h3 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}' {{action showPosterExpansion this}}>{{breakUp username}}</a></h3>
{{#if user_title}}<div class="user-title">{{user_title}}</div>{{/if}} {{#if user_title}}<div class="user-title" {{action showPosterExpansion this}}>{{user_title}}</div>{{/if}}
</div> </div>
{{else}} {{else}}
<div class="contents"> <div class="contents">

View File

@ -0,0 +1,18 @@
{{#if model}}
{{avatar model imageSize="huge"}}
<h1>{{username}}</h1>
<h2>{{name}}</h2>
<h3>{{i18n last_post}}: {{unboundDate last_posted_at}}</h3>
<div class='bottom'>
{{#if bio_cooked}}<div class='bio'>{{{bio_cooked}}}</div>{{/if}}
<button class='btn'><i class='icon icon-envelope'></i>{{i18n user.private_message}}</button>
{{#link-to 'user' model class="btn"}}<i class='icon icon-user'></i>{{i18n user.profile}}{{/link-to}}
<button class='btn' {{action togglePosts this}}><i class='icon icon-filter'></i>{{i18n topic.filter_to username="username"}}</button>
</div>
{{/if}}

View File

@ -125,6 +125,8 @@
</div> </div>
{{render share}} {{render share}}
{{render posterExpansion}}
{{#if currentUser.enable_quoting}} {{#if currentUser.enable_quoting}}
{{render quoteButton}} {{render quoteButton}}
{{/if}} {{/if}}
@ -132,3 +134,4 @@
{{#if currentUser.staff}} {{#if currentUser.staff}}
{{render topicAdminMenu content}} {{render topicAdminMenu content}}
{{/if}} {{/if}}

View File

@ -0,0 +1,43 @@
/**
Shows expanded details for a poster
@class PosterExpansionView
@namespace Discourse
@module Discourse
**/
Discourse.PosterExpansionView = Discourse.View.extend({
elementId: 'poster-expansion',
classNameBindings: ['controller.model::hidden'],
// Position the expansion when the model changes
_modelChanged: function() {
var post = this.get('controller.post'),
self = this;
Em.run.schedule('afterRender', function() {
if (post) {
var $post = $('#' + post.get('postElementId')),
$avatar = $('.topic-meta-data img.avatar', $post),
position = $avatar.offset();
position.left += $avatar.width() + 5;
self.$().css(position);
}
});
}.observes('controller.model'),
didInsertElement: function() {
var self = this;
$('html').on('mousedown.outside-poster-expansion', function(e) {
if (self.$().has(e.target).length !== 0) { return; }
self.get('controller').set('model', null);
return true;
});
},
willDestroyElement: function() {
$('html').off('mousedown.outside-poster-expansion');
}
});

View File

@ -0,0 +1,51 @@
// styles that apply to the "share" popup when sharing a link to a post or topic
@import "common/foundation/variables";
@import "common/foundation/mixins";
#poster-expansion {
position: absolute;
left: 20px;
z-index: 990;
@include border-radius-all(3px);
@include box-shadow(1px 1px 5px $darkish_gray);
background-color: $white;
padding: 7px 7px 6px 7px;
width: 400px;
h1 {
font-size: 30px;
line-height: 33px;
margin-bottom: 8px;
}
h2 {
font-size: 20px;
line-height: 22px;
font-weight: normal;
}
h3 {
font-size: 13px;
font-weight: normal;
margin-top: 5px;
}
.bottom {
clear: both;
padding-top: 10px;
}
img.avatar {
float: left;
padding-right: 10px;
}
p {
margin: 0 0 5px 0;
}
button {
margin: 0 0 7px 0;
}
}

View File

@ -30,6 +30,8 @@ class TopicsController < ApplicationController
opts = params.slice(:username_filters, :filter, :page, :post_number) opts = params.slice(:username_filters, :filter, :page, :post_number)
opts[:username_filters] = [opts[:username_filters]] if opts[:username_filters].is_a?(String)
begin begin
@topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts) @topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts)
rescue Discourse::NotFound rescue Discourse::NotFound

View File

@ -581,6 +581,7 @@ en:
title: Topic Rank Details title: Topic Rank Details
topic: topic:
filter_to: "Toggle only posts by {{username}} in this topic"
create_in: 'Create {{categoryName}} Topic' create_in: 'Create {{categoryName}} Topic'
create: 'Create Topic' create: 'Create Topic'
create_long: 'Create a new Topic' create_long: 'Create a new Topic'

View File

@ -25,3 +25,15 @@ test("isAllowedToUploadAFile", function() {
user.setProperties({ admin: false, moderator: true }); user.setProperties({ admin: false, moderator: true });
ok(user.isAllowedToUploadAFile("image"), "moderator can always upload a file"); ok(user.isAllowedToUploadAFile("image"), "moderator can always upload a file");
}); });
asyncTestDiscourse("findByUsername", function() {
expect(3);
Discourse.User.findByUsername('eviltrout').then(function (user) {
present(user);
equal(user.get('username'), 'eviltrout', 'it has the correct username');
equal(user.get('name'), 'Robin Ward', 'it has the full name since it has details');
start();
});
});