Each user activity filter has its own URL now.

This commit is contained in:
Robin Ward
2013-07-16 16:16:37 -04:00
parent 3238e0caa4
commit 19f3a8d640
28 changed files with 388 additions and 277 deletions

View File

@ -4,7 +4,7 @@
<div class='field'>{{i18n user.username.title}}</div> <div class='field'>{{i18n user.username.title}}</div>
<div class='value'>{{username}}</div> <div class='value'>{{username}}</div>
<div class='controls'> <div class='controls'>
{{#linkTo 'user.activity' content class="btn"}} {{#linkTo 'userActivity' class="btn"}}
<i class='icon icon-user'></i> <i class='icon icon-user'></i>
{{i18n admin.user.show_public_profile}} {{i18n admin.user.show_public_profile}}
{{/linkTo}} {{/linkTo}}

View File

@ -28,6 +28,26 @@ Discourse.computed = {
}).property(p1, p2); }).property(p1, p2);
}, },
/**
Returns i18n version of a string based on a property.
@method i18n
@params {String} properties* to format
@params {String} format the i18n format string
@return {Function} computedProperty function
**/
i18n: function() {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.pop();
var computed = Ember.computed(function() {
var context = this;
return I18n.t(format.fmt.apply(format, args.map(function (a) {
return context.get(a);
})));
});
return computed.property.apply(computed, args);
},
/** /**
Uses an Ember String `fmt` call to format a string. See: Uses an Ember String `fmt` call to format a string. See:
http://emberjs.com/api/classes/Ember.String.html#method_fmt http://emberjs.com/api/classes/Ember.String.html#method_fmt
@ -71,4 +91,5 @@ Discourse.computed = {
} }
}; };

View File

@ -1,3 +1,37 @@
Discourse.UserActivityRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('user_activity', {into: 'user', outlet: 'userOutlet' });
},
model: function() {
return this.modelFor('user');
}
});
Discourse.UserActivityIndexRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').findStream();
},
setupController: function(controller, model) {
this.controllerFor('userActivity').set('stream', model);
}
});
Object.keys(Discourse.UserAction.TYPES).forEach(function (userAction) {
Discourse["UserActivity" + userAction.classify() + "Route"] = Discourse.UserActivityIndexRoute.extend({
model: function() {
return this.modelFor('user').findStream(Discourse.UserAction.TYPES[userAction]);
}
});
});
Discourse.UserIndexRoute = Discourse.Route.extend({
redirect: function() {
this.transitionTo('userActivity.index');
}
});
/** /**
This controller supports all actions on a user's activity stream This controller supports all actions on a user's activity stream
@ -6,7 +40,7 @@
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
Discourse.UserActivityController = Discourse.ObjectController.extend({ Discourse.UserActivityController = Discourse.Controller.extend({
needs: ['composer'], needs: ['composer'],
kickOffPrivateMessage: (function() { kickOffPrivateMessage: (function() {

View File

@ -1,3 +1,68 @@
/**
Base route for showing private messages
@class UserPrivateMessagesRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('user_private_messages', {into: 'user', outlet: 'userOutlet' });
},
model: function() {
return this.modelFor('user');
},
setupController: function(controller, user) {
var composerController = this.controllerFor('composer');
controller.set('model', user);
Discourse.Draft.get('new_private_message').then(function(data) {
if (data.draft) {
composerController.open({
draft: data.draft,
draftKey: 'new_private_message',
ignoreIfChanged: true,
draftSequence: data.draft_sequence
});
}
});
}
});
/**
Default private messages route
@class UserPrivateMessagesIndexRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesIndexRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').findStream(Discourse.UserAction.TYPES.messages_received);
},
setupController: function(controller, model) {
this.controllerFor('userPrivateMessages').set('stream', model);
}
});
/**
Private messages sent route
@class UserPrivateMessagesSentRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesSentRoute = Discourse.UserPrivateMessagesIndexRoute.extend({
model: function() {
return this.modelFor('user').findStream(Discourse.UserAction.TYPES.messages_sent);
}
});
/** /**
This controller handles actions related to a user's private messages. This controller handles actions related to a user's private messages.

View File

@ -270,8 +270,12 @@ Discourse.User = Discourse.Model.extend({
}, },
findStream: function(filter) { findStream: function(filter) {
if (Discourse.UserAction.statGroups[filter]) {
filter = Discourse.UserAction.statGroups[filter].join(","); // When filtering for replies, include mentions and quotes too
if (filter === Discourse.UserAction.TYPES.replies) {
filter = [Discourse.UserAction.TYPES.replies,
Discourse.UserAction.TYPES.mentions,
Discourse.UserAction.TYPES.quotes].join(",");
} }
var stream = Discourse.UserStream.create({ var stream = Discourse.UserStream.create({
@ -351,7 +355,7 @@ Discourse.User.reopenClass({
groupStats: function(stats) { groupStats: function(stats) {
var responses = Discourse.UserActionStat.create({ var responses = Discourse.UserActionStat.create({
count: 0, count: 0,
action_type: Discourse.UserAction.RESPONSE action_type: Discourse.UserAction.TYPES.replies
}); });
stats.filterProperty('isResponse').forEach(function (stat) { stats.filterProperty('isResponse').forEach(function (stat) {
@ -363,7 +367,7 @@ Discourse.User.reopenClass({
var insertAt = 1; var insertAt = 1;
result.forEach(function(item, index){ result.forEach(function(item, index){
if(item.action_type === Discourse.UserAction.NEW_TOPIC || item.action_type === Discourse.UserAction.POST){ if(item.action_type === Discourse.UserAction.TYPES.topics || item.action_type === Discourse.UserAction.TYPES.posts){
insertAt = index + 1; insertAt = index + 1;
} }
}); });

View File

@ -6,82 +6,100 @@
@namespace Discourse @namespace Discourse
@module Discourse @module Discourse
**/ **/
var UserActionTypes = {
likes_given: 1,
likes_received: 2,
bookmarks: 3,
topics: 4,
posts: 5,
replies: 6,
mentions: 7,
quotes: 9,
favorites: 10,
edits: 11,
messages_sent: 12,
messages_received: 13
};
var InvertedActionTypes = {};
_.each(UserActionTypes, function (k, v) {
InvertedActionTypes[k] = v;
});
Discourse.UserAction = Discourse.Model.extend({ Discourse.UserAction = Discourse.Model.extend({
descriptionHtml: function() { /**
var action = this.get('action_type'); Return an i18n key we will use for the description text of a user action.
var ua = Discourse.UserAction;
var actions = [ua.LIKE, ua.WAS_LIKED, ua.STAR, ua.EDIT, ua.BOOKMARK, ua.GOT_PRIVATE_MESSAGE, ua.NEW_PRIVATE_MESSAGE];
var icon = "";
var sentence = "";
var sameUser = (this.get('username') === Discourse.User.current('username'));
if (action === null || actions.indexOf(action) >= 0) { @property descriptionKey
**/
descriptionKey: function() {
var action = this.get('action_type');
if (action === null || Discourse.UserAction.TO_SHOW.indexOf(action) >= 0) {
if (this.get('isPM')) { if (this.get('isPM')) {
icon = '<i class="icon icon-envelope" title="{{i18n user.stream.private_message}}"></i>'; return this.get('sameUser') ? 'sent_by_you' : 'sent_by_user';
if (sameUser) {
sentence = I18n.t('user_action.sent_by_you', { userUrl: this.get('userUrl') });
} else {
sentence = I18n.t('user_action.sent_by_user', { user: this.get('name'), userUrl: this.get('userUrl') });
}
} else { } else {
if (sameUser) { return this.get('sameUser') ? 'posted_by_you' : 'posted_by_user';
sentence = I18n.t('user_action.posted_by_you', { userUrl: this.get('userUrl') });
} else {
sentence = I18n.t('user_action.posted_by_user', { user: this.get('name'), userUrl: this.get('userUrl') });
}
} }
} else if (action === ua.NEW_TOPIC) {
if (sameUser) {
sentence = I18n.t('user_action.you_posted_topic', { userUrl: this.get('userUrl'), topicUrl: this.get('replyUrl') });
} else {
sentence = I18n.t('user_action.user_posted_topic', { user: this.get('name'), userUrl: this.get('userUrl'), topicUrl: this.get('replyUrl') });
}
} else if (action === ua.POST || action === ua.RESPONSE) {
if (this.get('reply_to_post_number')) {
if (sameUser) {
sentence = I18n.t('user_action.you_replied_to_post', { post_number: '#' + this.get('reply_to_post_number'),
userUrl: this.get('userUrl'), postUrl: this.get('postUrl') });
} else {
sentence = I18n.t('user_action.user_replied_to_post', { user: this.get('name'),
post_number: '#' + this.get('reply_to_post_number'), userUrl: this.get('userUrl'), postUrl: this.get('postUrl') });
}
} else {
if (sameUser) {
sentence = I18n.t('user_action.you_replied_to_topic', { userUrl: this.get('userUrl'),
topicUrl: this.get('replyUrl') });
} else {
sentence = I18n.t('user_action.user_replied_to_topic', { user: this.get('name'),
userUrl: this.get('userUrl'), topicUrl: this.get('replyUrl') });
}
}
} else if (action === ua.MENTION) {
if (sameUser) {
sentence = I18n.t('user_action.you_mentioned_user', { user: this.get('target_name'),
user1Url: this.get('userUrl'), user2Url: this.get('targetUserUrl') });
} else {
if (this.get('target_username') === Discourse.User.current('username')) {
sentence = I18n.t('user_action.user_mentioned_you', { user: this.get('name'),
user1Url: this.get('userUrl'), user2Url: this.get('targetUserUrl') });
} else {
sentence = I18n.t('user_action.user_mentioned_user', { user: this.get('name'),
another_user: this.get('target_name'), user1Url: this.get('userUrl'), user2Url: this.get('targetUserUrl') });
}
}
} else {
return "";
} }
return new Handlebars.SafeString(icon + " " + sentence); if (this.get('topicType')) {
}.property(), return this.get('sameUser') ? 'you_posted_topic' : 'user_posted_topic';
}
targetUserUrl: function() { if (this.get('postReplyType')) {
return Discourse.Utilities.userUrl(this.get('target_username')); if (this.get('reply_to_post_number')) {
}.property(), return this.get('sameUser') ? 'you_replied_to_post' : 'user_replied_to_post';
} else {
return this.get('sameUser') ? 'you_replied_to_topic' : 'user_replied_to_topic';
}
}
userUrl: function() { if (this.get('mentionType')) {
return Discourse.Utilities.userUrl(this.get('username')); if (this.get('sameUser')) {
}.property(), return 'you_mentioned_user';
} else {
return this.get('targetUser') ? 'user_mentioned_you' : 'user_mentioned_user';
}
}
}.property('action_type'),
/**
Returns the HTML representation of a user action's description, complete with icon.
@property descriptionHtml
**/
descriptionHtml: function() {
var descriptionKey = this.get('descriptionKey');
if (!descriptionKey) { return; }
var icon = this.get('isPM') ? '<i class="icon icon-envelope" title="{{i18n user.stream.private_message}}"></i>' : '';
return new Handlebars.SafeString(icon + " " + I18n.t("user_action." + descriptionKey, {
userUrl: this.get('userUrl'),
replyUrl: this.get('replyUrl'),
postUrl: this.get('postUrl'),
topicUrl: this.get('replyUrl'),
user: this.get('name'),
post_number: '#' + this.get('reply_to_post_number'),
user1Url: this.get('userUrl'),
user2Url: this.get('targetUserUrl'),
another_user: this.get('target_name')
}));
}.property('descriptionKey'),
sameUser: function() {
return this.get('username') === Discourse.User.current('username');
}.property('username'),
targetUser: function() {
return this.get('target_username') === Discourse.User.current('username');
}.property('target_username'),
targetUserUrl: Discourse.computed.url('target_username', '/users/%@'),
userUrl: Discourse.computed.url('username', '/users/%@'),
postUrl: function() { postUrl: function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number')); return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number'));
@ -91,15 +109,14 @@ Discourse.UserAction = Discourse.Model.extend({
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('reply_to_post_number')); return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('reply_to_post_number'));
}.property(), }.property(),
isPM: function() { replyType: Em.computed.equal('action_type', UserActionTypes.replies),
var a = this.get('action_type'); postType: Em.computed.equal('action_type', UserActionTypes.posts),
return a === Discourse.UserAction.NEW_PRIVATE_MESSAGE || a === Discourse.UserAction.GOT_PRIVATE_MESSAGE; topicType: Em.computed.equal('action_type', UserActionTypes.topics),
}.property(), messageSentType: Em.computed.equal('action_type', UserActionTypes.messages_sent),
messageReceivedType: Em.computed.equal('action_type', UserActionTypes.messages_received),
isPostAction: function() { mentionType: Em.computed.equal('action_type', UserActionTypes.mentions),
var a = this.get('action_type'); isPM: Em.computed.or('messageSentType', 'messageReceivedType'),
return a === Discourse.UserAction.RESPONSE || a === Discourse.UserAction.POST || a === Discourse.UserAction.NEW_TOPIC; postReplyType: Em.computed.or('postType', 'replyType'),
}.property(),
addChild: function(action) { addChild: function(action) {
var groups = this.get("childGroups"); var groups = this.get("childGroups");
@ -113,17 +130,16 @@ Discourse.UserAction = Discourse.Model.extend({
} }
this.set("childGroups", groups); this.set("childGroups", groups);
var ua = Discourse.UserAction;
var bucket = (function() { var bucket = (function() {
switch (action.action_type) { switch (action.action_type) {
case ua.LIKE: case UserActionTypes.likes_given:
case ua.WAS_LIKED: case UserActionTypes.likes_received:
return "likes"; return "likes";
case ua.STAR: case UserActionTypes.favorites:
return "stars"; return "stars";
case ua.EDIT: case UserActionTypes.edits:
return "edits"; return "edits";
case ua.BOOKMARK: case UserActionTypes.bookmarks:
return "bookmarks"; return "bookmarks";
} }
})(); })();
@ -145,28 +161,29 @@ Discourse.UserAction = Discourse.Model.extend({
}.property("childGroups"), }.property("childGroups"),
switchToActing: function() { switchToActing: function() {
this.set('username', this.get('acting_username')); this.setProperties({
this.set('avatar_template', this.get('acting_avatar_template')); username: this.get('acting_username'),
this.set('name', this.get('acting_name')); avatar_template: this.get('acting_avatar_template'),
name: this.get('acting_name')
});
} }
}); });
Discourse.UserAction.reopenClass({ Discourse.UserAction.reopenClass({
collapseStream: function(stream) { collapseStream: function(stream) {
var collapse, collapsed, pos, uniq; var uniq = {},
collapse = [this.LIKE, this.WAS_LIKED, this.STAR, this.EDIT, this.BOOKMARK]; collapsed = Em.A(),
uniq = {}; pos = 0;
collapsed = Em.A();
pos = 0; stream.forEach(function(item) {
_.each(stream, function(item) { var key = "" + item.topic_id + "-" + item.post_number;
var current, found, key; var found = uniq[key];
key = "" + item.topic_id + "-" + item.post_number;
found = uniq[key];
if (found === void 0) { if (found === void 0) {
if (collapse.indexOf(item.action_type) >= 0) {
var current;
if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
current = Discourse.UserAction.create(item); current = Discourse.UserAction.create(item);
current.set('action_type', null); current.setProperties({action_type: null, description: null});
current.set('description', null);
item.switchToActing(); item.switchToActing();
current.addChild(item); current.addChild(item);
} else { } else {
@ -176,39 +193,37 @@ Discourse.UserAction.reopenClass({
collapsed[pos] = current; collapsed[pos] = current;
pos += 1; pos += 1;
} else { } else {
if (collapse.indexOf(item.action_type) >= 0) { if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
item.switchToActing(); item.switchToActing();
return collapsed[found].addChild(item); collapsed[found].addChild(item);
} else { } else {
collapsed[found].set('action_type', item.get('action_type')); collapsed[found].setProperties(item.getProperties('action_type', 'description'));
return collapsed[found].set('description', item.get('description'));
} }
} }
}); });
return collapsed; return collapsed;
}, },
// in future we should be sending this through from the server TYPES: UserActionTypes,
LIKE: 1, TYPES_INVERTED: InvertedActionTypes,
WAS_LIKED: 2,
BOOKMARK: 3, TO_COLLAPSE: [UserActionTypes.likes_given,
NEW_TOPIC: 4, UserActionTypes.likes_received,
POST: 5, UserActionTypes.favorites,
RESPONSE: 6, UserActionTypes.edits,
MENTION: 7, UserActionTypes.bookmarks],
QUOTE: 9,
STAR: 10, TO_SHOW: [
EDIT: 11, UserActionTypes.likes_given,
NEW_PRIVATE_MESSAGE: 12, UserActionTypes.likes_received,
GOT_PRIVATE_MESSAGE: 13 UserActionTypes.favorites,
}); UserActionTypes.edits,
UserActionTypes.bookmarks,
UserActionTypes.messages_sent,
UserActionTypes.messages_received
]
Discourse.UserAction.reopenClass({
statGroups: (function() {
var g = {};
g[Discourse.UserAction.RESPONSE] = [Discourse.UserAction.RESPONSE, Discourse.UserAction.MENTION, Discourse.UserAction.QUOTE];
return g;
})()
}); });

View File

@ -10,19 +10,17 @@ Discourse.UserActionStat = Discourse.Model.extend({
isPM: function() { isPM: function() {
var actionType = this.get('action_type'); var actionType = this.get('action_type');
return actionType === Discourse.UserAction.NEW_PRIVATE_MESSAGE || return actionType === Discourse.UserAction.TYPES.messages_sent ||
actionType === Discourse.UserAction.GOT_PRIVATE_MESSAGE; actionType === Discourse.UserAction.TYPES.messages_received;
}.property('action_type'), }.property('action_type'),
description: function() { description: Discourse.computed.i18n('action_type', 'user_action_groups.%@'),
return I18n.t('user_action_groups.' + this.get('action_type'));
}.property('description'),
isResponse: function() { isResponse: function() {
var actionType = this.get('action_type'); var actionType = this.get('action_type');
return actionType === Discourse.UserAction.RESPONSE || return actionType === Discourse.UserAction.TYPES.replies ||
actionType === Discourse.UserAction.MENTION || actionType === Discourse.UserAction.TYPES.mentions ||
actionType === Discourse.UserAction.QUOTE; actionType === Discourse.UserAction.TYPES.quotes;
}.property('action_type') }.property('action_type')
}); });

View File

@ -40,12 +40,24 @@ Discourse.Route.buildRoutes(function() {
// User routes // User routes
this.resource('user', { path: '/users/:username' }, function() { this.resource('user', { path: '/users/:username' }, function() {
this.route('activity', { path: '/' }); this.route('index', { path: '/'} );
this.resource('userActivity', { path: '/activity' }, function() {
var resource = this;
Object.keys(Discourse.UserAction.TYPES).forEach(function (userAction) {
resource.route(userAction, { path: userAction.replace("_", "-") });
});
});
this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
this.route('sent', {path: '/messages-sent'});
});
this.resource('preferences', { path: '/preferences' }, function() { this.resource('preferences', { path: '/preferences' }, function() {
this.route('username', { path: '/username' }); this.route('username', { path: '/username' });
this.route('email', { path: '/email' }); this.route('email', { path: '/email' });
}); });
this.route('privateMessages', { path: '/private-messages' });
this.route('invited', { path: 'invited' }); this.route('invited', { path: 'invited' });
}); });
}); });

View File

@ -11,7 +11,7 @@ Discourse.RestrictedUserRoute = Discourse.Route.extend({
afterModel: function() { afterModel: function() {
var user = this.modelFor('user'); var user = this.modelFor('user');
if (!user.get('can_edit')) { if (!user.get('can_edit')) {
this.transitionTo('user.activity', user); this.transitionTo('userActivity');
} }
} }

View File

@ -1,21 +0,0 @@
/**
This route handles shows a user's activity
@class UserActivityRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').findStream();
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
}
});

View File

@ -1,37 +0,0 @@
/**
This route displays a user's private messages.
@class UserPrivateMessagesRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user').findStream(Discourse.UserAction.GOT_PRIVATE_MESSAGE);
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
setupController: function(controller, stream) {
var composerController = this.controllerFor('composer');
controller.set('model', stream);
Discourse.Draft.get('new_private_message').then(function(data) {
if (data.draft) {
composerController.open({
draft: data.draft,
draftKey: 'new_private_message',
ignoreIfChanged: true,
draftSequence: data.draft_sequence
});
}
});
}
});

View File

@ -85,7 +85,7 @@
</li> </li>
<li class='current-user'> <li class='current-user'>
{{#if currentUser}} {{#if currentUser}}
{{#titledLinkTo 'user.activity' currentUser titleKey="current_user" class="icon"}}{{avatar currentUser imageSize="medium" }}{{/titledLinkTo}} {{#titledLinkTo 'user' currentUser titleKey="current_user" class="icon"}}{{avatar currentUser imageSize="medium" }}{{/titledLinkTo}}
{{else}} {{else}}
<div class="icon not-logged-in-avatar" {{action showLogin}}><i class='icon-user'></i></div> <div class="icon not-logged-in-avatar" {{action showLogin}}><i class='icon-user'></i></div>
{{/if}} {{/if}}

View File

@ -1,4 +1,4 @@
{{#with user}} {{#with model}}
<div id='user-info'> <div id='user-info'>
<nav class='buttons'> <nav class='buttons'>
{{#if can_edit}} {{#if can_edit}}
@ -15,9 +15,9 @@
<div class='clearfix'></div> <div class='clearfix'></div>
<ul class='action-list nav-stacked side-nav'> <ul class='action-list nav-stacked side-nav'>
{{activityFilter count=statsCountNonPM}} {{activityFilter count=statsCountNonPM user=this}}
{{#each statsExcludingPms}} {{#each stat in statsExcludingPms}}
{{activityFilter content=this}} {{activityFilter content=stat user=model}}
{{/each}} {{/each}}
</ul> </ul>
<div class='show'> <div class='show'>
@ -35,7 +35,7 @@
<dt>{{i18n user.last_seen}}:</dt><dd>{{date last_seen_at}}</dd> <dt>{{i18n user.last_seen}}:</dt><dd>{{date last_seen_at}}</dd>
{{/if}} {{/if}}
{{#if invited_by}} {{#if invited_by}}
<dt>{{i18n user.invited_by}}:</dt><dd>{{#linkTo 'user.activity' invited_by}}{{invited_by.username}}{{/linkTo}}</dd> <dt>{{i18n user.invited_by}}:</dt><dd>{{#linkTo 'userActivity' invited_by}}{{invited_by.username}}{{/linkTo}}</dd>
{{/if}} {{/if}}
{{#if email}} {{#if email}}
<dt>{{i18n user.email.title}}:</dt><dd {{bindAttr title="email"}}>{{email}}</dd> <dt>{{i18n user.email.title}}:</dt><dd {{bindAttr title="email"}}>{{email}}</dd>
@ -53,4 +53,4 @@
</div> </div>
{{/with}} {{/with}}
{{userStream stream=model}} {{userStream stream=stream}}

View File

@ -1,5 +1,5 @@
<div id='user-info'> <div id='user-info'>
{{#with user}} {{#with model}}
<nav class='buttons'> <nav class='buttons'>
{{#if can_edit}} {{#if can_edit}}
{{#linkTo "preferences" class="btn"}}{{i18n user.edit}}{{/linkTo}} {{#linkTo "preferences" class="btn"}}{{i18n user.edit}}{{/linkTo}}
@ -15,11 +15,12 @@
<div class='clearfix'></div> <div class='clearfix'></div>
<ul class='action-list nav-stacked side-nav'> <ul class='action-list nav-stacked side-nav'>
{{#each statsPmsOnly}} {{#each stat in statsPmsOnly}}
{{activityFilter content=this}} {{activityFilter content=stat user=model}}
{{/each}} {{/each}}
</ul> </ul>
{{/with}} {{/with}}
</div> </div>
{{userStream stream=model}}
{{userStream stream=stream}}

View File

@ -1,6 +1,6 @@
<div id='user-stream'> <div id='user-stream'>
{{#each view.stream.content}} {{#each view.stream.content}}
<div {{bindAttr class=":item hidden:hidden deleted:deleted"}}> <div {{bindAttr class=":item hidden deleted"}}>
<div class='clearfix info'> <div class='clearfix info'>
<a href="{{unbound userUrl}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar this imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a> <a href="{{unbound userUrl}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar this imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
<span class='time'>{{date path="created_at" leaveAgo="true"}}</span> <span class='time'>{{date path="created_at" leaveAgo="true"}}</span>

View File

@ -12,11 +12,11 @@
{{/if}} {{/if}}
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li> <li>
{{#linkTo 'user.activity'}}{{i18n user.activity_stream}}{{/linkTo}} {{#linkTo 'userActivity'}}{{i18n user.activity_stream}}{{/linkTo}}
</li> </li>
{{#if canSeePrivateMessages}} {{#if canSeePrivateMessages}}
<li> <li>
{{#linkTo "user.privateMessages"}}{{i18n user.private_messages}}{{/linkTo}} {{#linkTo 'userPrivateMessages'}}{{i18n user.private_messages}}{{/linkTo}}
</li> </li>
{{/if}} {{/if}}
<li> <li>

View File

@ -30,7 +30,6 @@ Discourse.ActionsHistoryView = Discourse.View.extend({
if (c.get('usersExpanded')) { if (c.get('usersExpanded')) {
var postUrl; var postUrl;
c.get('users').forEach(function(u) { c.get('users').forEach(function(u) {
console.log(u);
iconsHtml += "<a href=\"" + Discourse.getURL("/users/") + (u.get('username_lower')) + "\">"; iconsHtml += "<a href=\"" + Discourse.getURL("/users/") + (u.get('username_lower')) + "\">";
if (u.post_url) { if (u.post_url) {
postUrl = postUrl || u.post_url; postUrl = postUrl || u.post_url;

View File

@ -10,9 +10,8 @@ Discourse.ActivityFilterView = Discourse.View.extend({
tagName: 'li', tagName: 'li',
classNameBindings: ['active', 'noGlyph'], classNameBindings: ['active', 'noGlyph'],
stream: Em.computed.alias('controller.content'), stream: Em.computed.alias('controller.stream'),
shouldRerender: Discourse.View.renderIfChanged('count'), shouldRerender: Discourse.View.renderIfChanged('count'),
noGlyph: Em.computed.empty('icon'), noGlyph: Em.computed.empty('icon'),
active: function() { active: function() {
@ -24,62 +23,57 @@ Discourse.ActivityFilterView = Discourse.View.extend({
} }
}.property('stream.filter', 'content.action_type'), }.property('stream.filter', 'content.action_type'),
activityCount: function() {
return this.get('content.count') || this.get('count');
}.property('content.count', 'count'),
typeKey: function() {
var actionType = this.get('content.action_type');
if (actionType === Discourse.UserAction.TYPES.messages_received) { return ""; }
var result = Discourse.UserAction.TYPES_INVERTED[actionType];
if (!result) { return ""; }
// We like our URLS to have hyphens, not underscores
return "/" + result.replace("_", "-");
}.property('content.action_type'),
url: function() {
var section = this.get('content.isPM') ? "/private-messages" : "/activity";
return "/users/" + this.get('user.username_lower') + section + this.get('typeKey');
}.property('typeKey'),
description: function() {
return this.get('content.description') || I18n.t("user.filters.all");
}.property('content.description'),
render: function(buffer) { render: function(buffer) {
var content = this.get("content"); buffer.push("<a href='" + this.get('url') + "'>");
var count, description;
if (content) {
count = Em.get(content, "count");
description = Em.get(content, "description");
} else {
count = this.get("count");
description = I18n.t("user.filters.all");
}
var icon = this.get('icon'); var icon = this.get('icon');
if (icon) {
buffer.push("<a href='#'>");
if(icon) {
buffer.push("<i class='glyph icon icon-" + icon + "'></i> "); buffer.push("<i class='glyph icon icon-" + icon + "'></i> ");
} }
buffer.push(description + buffer.push(this.get('description') + " <span class='count'>(" + this.get('activityCount') + ")</span>");
" <span class='count'>(" + count + ")</span>");
buffer.push("<span class='icon-chevron-right'></span></a>"); buffer.push("<span class='icon-chevron-right'></span></a>");
}, },
icon: function(){ icon: function(){
var action_type = parseInt(this.get("content.action_type"),10); switch(parseInt(this.get('content.action_type'),10)) {
var icon; case Discourse.UserAction.TYPES.likes_received:
return "heart";
switch(action_type){ case Discourse.UserAction.TYPES.bookmarks:
case Discourse.UserAction.WAS_LIKED: return "bookmark";
icon = "heart"; case Discourse.UserAction.TYPES.edits:
break; return "pencil";
case Discourse.UserAction.BOOKMARK: case Discourse.UserAction.TYPES.replies:
icon = "bookmark"; return "reply";
break; case Discourse.UserAction.TYPES.favorites:
case Discourse.UserAction.EDIT: return "star";
icon = "pencil";
break;
case Discourse.UserAction.RESPONSE:
icon = "reply";
break;
case Discourse.UserAction.STAR:
icon = "star";
break;
} }
}.property("content.action_type")
return icon;
}.property("content.action_type"),
click: function() {
this.set('stream.filter', this.get('content.action_type'));
return false;
}
}); });
Discourse.View.registerHelper('activityFilter', Discourse.ActivityFilterView); Discourse.View.registerHelper('activityFilter', Discourse.ActivityFilterView);

View File

@ -18,12 +18,12 @@ Discourse.UserPrivateMessagesView = Discourse.View.extend({
inbox: function(evt) { inbox: function(evt) {
this.selectCurrent(evt); this.selectCurrent(evt);
return this.set('controller.filter', Discourse.UserAction.GOT_PRIVATE_MESSAGE); return this.set('controller.filter', Discourse.UserAction.TYPES.messages_received);
}, },
sentMessages: function(evt) { sentMessages: function(evt) {
this.selectCurrent(evt); this.selectCurrent(evt);
return this.set('controller.filter', Discourse.UserAction.NEW_PRIVATE_MESSAGE); return this.set('controller.filter', Discourse.UserAction.TYPES.messages_sent);
} }
}); });

View File

@ -11,15 +11,17 @@
// Stuff we need to load first // Stuff we need to load first
//= require_tree ./discourse/mixins //= require_tree ./discourse/mixins
//= require ./discourse/components/computed
//= require ./discourse/views/view //= require ./discourse/views/view
//= require ./discourse/components/debounce //= require ./discourse/components/debounce
//= require ./discourse/models/model
//= require ./discourse/models/user_action
//= require ./discourse/controllers/controller //= require ./discourse/controllers/controller
//= require ./discourse/controllers/object_controller //= require ./discourse/controllers/object_controller
//= require ./discourse/views/modal/modal_body_view //= require ./discourse/views/modal/modal_body_view
//= require ./discourse/views/combobox_view //= require ./discourse/views/combobox_view
//= require ./discourse/views/buttons/button_view //= require ./discourse/views/buttons/button_view
//= require ./discourse/views/buttons/dropdown_button_view //= require ./discourse/views/buttons/dropdown_button_view
//= require ./discourse/models/model
//= require ./discourse/routes/discourse_route //= require ./discourse/routes/discourse_route
//= require ./discourse/routes/discourse_restricted_user_route //= require ./discourse/routes/discourse_restricted_user_route

View File

@ -119,6 +119,7 @@ Discourse::Application.routes.draw do
get 'user_preferences' => 'users#user_preferences_redirect' get 'user_preferences' => 'users#user_preferences_redirect'
get 'users/:username/private-messages' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT} get 'users/:username/private-messages' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/private-messages/:filter' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT} get 'users/:username' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
put 'users/:username' => 'users#update', constraints: {username: USERNAME_ROUTE_FORMAT} put 'users/:username' => 'users#update', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/preferences' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences get 'users/:username/preferences' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences
@ -129,6 +130,8 @@ Discourse::Application.routes.draw do
get 'users/:username/avatar(/:size)' => 'users#avatar', constraints: {username: USERNAME_ROUTE_FORMAT} get 'users/:username/avatar(/:size)' => 'users#avatar', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/invited' => 'users#invited', constraints: {username: USERNAME_ROUTE_FORMAT} get 'users/:username/invited' => 'users#invited', constraints: {username: USERNAME_ROUTE_FORMAT}
post 'users/:username/send_activation_email' => 'users#send_activation_email', constraints: {username: USERNAME_ROUTE_FORMAT} post 'users/:username/send_activation_email' => 'users#send_activation_email', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/activity' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/activity/:filter' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
resources :uploads resources :uploads

View File

@ -6,7 +6,14 @@ task 'integration:create_fixtures' => :environment do
fixtures = { fixtures = {
list: ["/latest.json", "/categories.json", "/category/bug.json"], list: ["/latest.json", "/categories.json", "/category/bug.json"],
topic: ["/t/280.json"], topic: ["/t/280.json"],
user: ["/users/eviltrout.json", "/user_actions.json?offset=0&username=eviltrout"], user: ["/users/eviltrout.json",
"/user_actions.json?offset=0&username=eviltrout",
"/user_actions.json?offset=0&username=eviltrout&filter=4",
"/user_actions.json?offset=0&username=eviltrout&filter=5",
"/user_actions.json?offset=0&username=eviltrout&filter=6,7,9",
"/user_actions.json?offset=0&username=eviltrout&filter=1",
"/user_actions.json?offset=0&username=eviltrout&filter=2",
"/user_actions.json?offset=0&username=eviltrout&filter=11"],
static: ["/faq", '/tos', '/privacy'] static: ["/faq", '/tos', '/privacy']
} }

View File

@ -50,7 +50,6 @@ test("fmt", function() {
test("url without a prefix", function() { test("url without a prefix", function() {
var t = testClass.create({ username: 'eviltrout' }); var t = testClass.create({ username: 'eviltrout' });
equal(t.get('userUrl'), "/users/eviltrout"); equal(t.get('userUrl'), "/users/eviltrout");
}); });
test("url with a prefix", function() { test("url with a prefix", function() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,21 @@
integration("User"); integration("User");
test("Profile", function() { test("Activity Streams", function() {
expect(14);
visit("/users/eviltrout").then(function() { var streamTest = function(url) {
expect(2); visit(url).then(function() {
ok(exists(".user-heading"), "The heading is rendered");
ok(exists("#user-stream"), "The stream is rendered");
});
};
ok(exists(".user-heading"), "The heading is rendered"); streamTest("/users/eviltrout");
ok(exists("#user-stream"), "The stream is rendered"); streamTest("/users/eviltrout/activity/topics");
}); streamTest("/users/eviltrout/activity/posts");
streamTest("/users/eviltrout/activity/replies");
streamTest("/users/eviltrout/activity/likes-given");
streamTest("/users/eviltrout/activity/likes-received");
streamTest("/users/eviltrout/activity/edits");
}); });

View File

@ -3,17 +3,17 @@ module("Discourse.UserAction");
test("collapsing likes", function () { test("collapsing likes", function () {
var actions = Discourse.UserAction.collapseStream([ var actions = Discourse.UserAction.collapseStream([
Discourse.UserAction.create({ Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE, action_type: Discourse.UserAction.TYPES.likes_given,
topic_id: 1, topic_id: 1,
user_id: 1, user_id: 1,
post_number: 1 post_number: 1
}), Discourse.UserAction.create({ }), Discourse.UserAction.create({
action_type: Discourse.UserAction.EDIT, action_type: Discourse.UserAction.TYPES.edits,
topic_id: 2, topic_id: 2,
user_id: 1, user_id: 1,
post_number: 1 post_number: 1
}), Discourse.UserAction.create({ }), Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE, action_type: Discourse.UserAction.TYPES.likes_given,
topic_id: 1, topic_id: 1,
user_id: 2, user_id: 2,
post_number: 1 post_number: 1