Show topics as a list of topics on the User Stream.

This commit is contained in:
Robin Ward 2013-07-24 17:15:21 -04:00
parent 3f5ea1ef79
commit 0317cf9608
57 changed files with 743 additions and 650 deletions

View File

@ -13,5 +13,3 @@
{{collection contentBinding="filteredContent" classNames="form-horizontal settings" itemViewClass="Discourse.SiteSettingView"}} {{collection contentBinding="filteredContent" classNames="form-horizontal settings" itemViewClass="Discourse.SiteSettingView"}}
<!-- will remove as soon as I figure out what is going on -->
<p><small>Diagnostics: last_message_processed {{diags.last_message_processed}}</small></p>

View File

@ -1,11 +1,9 @@
{{#with view.content}} <div class='span4 offset1'>
<div class='span4 offset1'> <h3>{{unbound setting}}</h3>
<h3>{{unbound setting}}</h3> </div>
</div> <div class="span11">
<div class="span11"> <label>
<label> {{view Ember.Checkbox checkedBinding="enabled" value="true"}}
{{view Ember.Checkbox checkedBinding="enabled" value="true"}} {{unbound description}}
{{unbound description}} </label>
</label> </div>
</div>
{{/with}}

View File

@ -1,19 +1,17 @@
{{#with view.content}} <div class='span4 offset1'>
<div class='span4 offset1'> <h3>{{unbound setting}}</h3>
<h3>{{unbound setting}}</h3> </div>
<div class="span11">
{{combobox valueAttribute="value" content=validValues value=value none=allowsNone}}
<div class='desc'>{{unbound description}}</div>
</div>
{{#if dirty}}
<div class='span3'>
<button class='btn ok' {{action save this}}><i class='icon-ok'></i></button>
<button class='btn cancel' {{action cancel this}}><i class='icon-remove'></i></button>
</div> </div>
<div class="span11"> {{else}}
{{combobox valueAttribute="value" content=validValues value=value none=allowsNone}} {{#if overridden}}
<div class='desc'>{{unbound description}}</div> <button class='btn' href='#' {{action resetDefault this}}>{{i18n admin.site_settings.reset}}</button>
</div>
{{#if dirty}}
<div class='span3'>
<button class='btn ok' {{action save this}}><i class='icon-ok'></i></button>
<button class='btn cancel' {{action cancel this}}><i class='icon-remove'></i></button>
</div>
{{else}}
{{#if overridden}}
<button class='btn' href='#' {{action resetDefault this}}>{{i18n admin.site_settings.reset}}</button>
{{/if}}
{{/if}} {{/if}}
{{/with}} {{/if}}

View File

@ -1,19 +1,17 @@
{{#with view.content}} <div class='span4 offset1'>
<div class='span4 offset1'> <h3>{{unbound setting}}</h3>
<h3>{{unbound setting}}</h3> </div>
<div class="span11">
{{textField value=value classNames="input-xxlarge"}}
<div class='desc'>{{unbound description}}</div>
</div>
{{#if dirty}}
<div class='span3'>
<button class='btn ok' {{action save this}}><i class='icon-ok'></i></button>
<button class='btn cancel' {{action cancel this}}><i class='icon-remove'></i></button>
</div> </div>
<div class="span11"> {{else}}
{{textField value=value classNames="input-xxlarge"}} {{#if overridden}}
<div class='desc'>{{unbound description}}</div> <button class='btn' href='#' {{action resetDefault this}}>{{i18n admin.site_settings.reset}}</button>
</div>
{{#if dirty}}
<div class='span3'>
<button class='btn ok' {{action save this}}><i class='icon-ok'></i></button>
<button class='btn cancel' {{action cancel this}}><i class='icon-remove'></i></button>
</div>
{{else}}
{{#if overridden}}
<button class='btn' href='#' {{action resetDefault this}}>{{i18n admin.site_settings.reset}}</button>
{{/if}}
{{/if}} {{/if}}
{{/with}} {{/if}}

View File

@ -64,6 +64,9 @@ Discourse.URL = Em.Object.createWithMixins({
if (this.navigatedToListMore(oldPath, path)) { return; } if (this.navigatedToListMore(oldPath, path)) { return; }
if (this.navigatedToHome(oldPath, path)) { return; } if (this.navigatedToHome(oldPath, path)) { return; }
if (path.match(/^\/?users\/[^\/]+$/)) {
path += "/activity";
}
// Be wary of looking up the router. In this case, we have links in our // Be wary of looking up the router. In this case, we have links in our
// HTML, say form compiled markdown posts, that need to be routed. // HTML, say form compiled markdown posts, that need to be routed.
var router = this.get('router'); var router = this.get('router');

View File

@ -87,7 +87,7 @@ Discourse.Utilities = {
}, },
userUrl: function(username) { userUrl: function(username) {
return Discourse.getURL("/users/" + username); return Discourse.getURL("/users/" + username.toLowerCase());
}, },
emailValid: function(email) { emailValid: function(email) {

View File

@ -24,6 +24,16 @@ Discourse.ListController = Discourse.Controller.extend({
}); });
}.property(), }.property(),
createTopicText: function() {
if (this.get('category.name')) {
return I18n.t("topic.create_in", {
categoryName: this.get('category.name')
});
} else {
return I18n.t("topic.create");
}
}.property('category.name'),
/** /**
Refresh our current topic list Refresh our current topic list

View File

@ -81,11 +81,11 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({
}.property('allLoaded', 'topics.length'), }.property('allLoaded', 'topics.length'),
loadMore: function() { loadMore: function() {
this.set('loadingMore', true); var topicList = this.get('model');
var listTopicsController = this; return topicList.loadMoreTopics().then(function(moreUrl) {
return this.get('model').loadMoreTopics().then(function(hasMoreTopics) { if (!Em.isEmpty(moreUrl)) {
listTopicsController.set('loadingMore', false); Discourse.URL.replaceState(Discourse.getURL("/") + topicList.get('filter') + "/more");
return hasMoreTopics; }
}); });
} }

View File

@ -1,51 +1,3 @@
/**
The route for editing a user's "About Me" bio.
@class PreferencesAboutRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesAboutRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
setupController: function(controller, model) {
controller.setProperties({ model: model, newBio: model.get('bio_raw') });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
},
events: {
changeAbout: function() {
var route = this;
var controller = route.controllerFor('preferencesAbout');
controller.setProperties({ saving: true });
return controller.get('model').save().then(function() {
controller.set('saving', false);
route.transitionTo('user.index');
}, function() {
// model failed to save
controller.set('saving', false);
alert(I18n.t('generic_error'));
});
}
}
});
/** /**
This controller supports actions related to updating your "About Me" bio This controller supports actions related to updating your "About Me" bio

View File

@ -1,21 +1,3 @@
/**
The common route stuff for a user's preference
@class PreferencesRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
}
});
/** /**
This controller supports actions related to updating one's preferences This controller supports actions related to updating one's preferences

View File

@ -1,32 +1,3 @@
/**
The route for editing a user's email
@class PreferencesEmailRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesEmailRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
setupController: function(controller, model) {
controller.setProperties({ model: model, newEmail: model.get('email') });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
}
});
/** /**
This controller supports actions related to updating one's email address This controller supports actions related to updating one's email address

View File

@ -1,32 +1,3 @@
/**
The route for updating a user's username
@class PreferencesUsernameRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
return this.render({ into: 'user', outlet: 'userOutlet' });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
},
setupController: function(controller, user) {
controller.setProperties({ model: user, newUsername: user.get('username') });
}
});
/** /**
This controller supports actions related to updating one's username This controller supports actions related to updating one's username

View File

@ -1,82 +1,3 @@
/**
The base route for showing an activity stream.
@class UserActivityRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('user_activity', {into: 'user', outlet: 'userOutlet' });
},
model: function() {
return this.modelFor('user');
},
setupController: function(controller, user) {
this.controllerFor('userActivity').set('model', user);
var composerController = this.controllerFor('composer');
controller.set('model', user);
if (Discourse.User.current()) {
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
});
}
});
}
}
});
Discourse.UserActivityIndexRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').findStream(this.get('userActionType'));
},
renderTemplate: function() {
this.render('user_stream', {into: 'user_activity', outlet: 'activity'});
},
setupController: function() {
this.controllerFor('user_activity').set('userActionType', this.get('userActionType'));
}
});
Discourse.UserIndexRoute = Discourse.UserActivityRoute.extend({
renderTemplate: function() {
this._super();
this.render('user_stream', {into: 'user_activity', outlet: 'activity'});
},
model: function() {
return this.modelFor('user').findStream();
},
setupController: function(controller, model) {
this.controllerFor('userActivity').set('model', this.modelFor('user'));
this.set('model', model);
}
});
// Build all the filter routes
Object.keys(Discourse.UserAction.TYPES).forEach(function (userAction) {
Discourse["UserActivity" + userAction.classify() + "Route"] = Discourse.UserActivityIndexRoute.extend({
userActionType: Discourse.UserAction.TYPES[userAction]
});
});
// // Build the private message routes
Discourse.UserPrivateMessagesRoute = Discourse.UserActivityRoute.extend({});
Discourse.UserPrivateMessagesIndexRoute = Discourse.UserActivityMessagesReceivedRoute;
Discourse.UserPrivateMessagesSentRoute = Discourse.UserActivityMessagesSentRoute;
/** /**
This controller supports all actions on a user's activity stream This controller supports all actions on a user's activity stream

View File

@ -0,0 +1,39 @@
/**
This mixin provides the ability to load more items for a view which is
scrolled to the bottom.
@class Discourse.LoadMore
@extends Ember.Mixin
@uses Discourse.Scrolling
@namespace Discourse
@module Discourse
**/
Discourse.LoadMore = Em.Mixin.create(Discourse.Scrolling, {
scrolled: function(e) {
var eyeline = this.get('eyeline');
if (eyeline) { eyeline.update(); }
},
loadMore: function() {
console.error('loadMore() not defined');
},
didInsertElement: function() {
this._super();
var eyeline = new Discourse.Eyeline(this.get('eyelineSelector'));
this.set('eyeline', eyeline);
var paginatedTopicListView = this;
eyeline.on('sawBottom', function() {
paginatedTopicListView.loadMore();
});
this.bindScrolling();
},
willRemoveElement: function() {
this._super();
this.unbindScrolling();
}
});

View File

@ -23,32 +23,37 @@ Discourse.TopicList = Discourse.Model.extend({
}, },
loadMoreTopics: function() { loadMoreTopics: function() {
var moreUrl, _this = this;
if (moreUrl = this.get('more_topics_url')) { if (this.get('loadingMore')) { return Ember.RSVP.reject(); }
Discourse.URL.replaceState(Discourse.getURL("/") + (this.get('filter')) + "/more");
var moreUrl = this.get('more_topics_url');
if (moreUrl) {
var topicList = this;
this.set('loadingMore', true);
return Discourse.ajax({url: moreUrl}).then(function (result) { return Discourse.ajax({url: moreUrl}).then(function (result) {
var newTopics, topics, topicsAdded = 0; var topicsAdded = 0;
if (result) { if (result) {
// the new topics loaded from the server // the new topics loaded from the server
newTopics = Discourse.TopicList.topicsFrom(result); var newTopics = Discourse.TopicList.topicsFrom(result);
topics = _this.get("topics"); var topics = topicList.get("topics");
_this.forEachNew(newTopics, function(t) { topicList.forEachNew(newTopics, function(t) {
t.set('highlight', topicsAdded++ === 0); t.set('highlight', topicsAdded++ === 0);
topics.pushObject(t); topics.pushObject(t);
}); });
_this.set('more_topics_url', result.topic_list.more_topics_url); topicList.set('more_topics_url', result.topic_list.more_topics_url);
Discourse.set('transient.topicsList', _this); Discourse.set('transient.topicsList', topicList);
topicList.set('loadingMore', false);
return result.topic_list.more_topics_url;
} }
return result.topic_list.more_topics_url;
}); });
} else { } else {
// Return a promise indicating no more results // Return a promise indicating no more results
return Ember.Deferred.promise(function (p) { return Ember.RSVP.reject();
p.resolve(false);
});
} }
}, },
@ -109,6 +114,9 @@ Discourse.TopicList.reopenClass({
categories = this.extractByKey(result.categories, Discourse.Category); categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User); users = this.extractByKey(result.users, Discourse.User);
topics = Em.A(); topics = Em.A();
console.log(result.topic_list);
_.each(result.topic_list.topics,function(ft) { _.each(result.topic_list.topics,function(ft) {
ft.category = categories[ft.category_id]; ft.category = categories[ft.category_id];
_.each(ft.posters,function(p) { _.each(ft.posters,function(p) {

View File

@ -264,6 +264,7 @@ 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;
}); });

View File

@ -99,7 +99,11 @@ Discourse.UserAction = Discourse.Model.extend({
}.property('target_username'), }.property('target_username'),
targetUserUrl: Discourse.computed.url('target_username', '/users/%@'), targetUserUrl: Discourse.computed.url('target_username', '/users/%@'),
userUrl: Discourse.computed.url('username', '/users/%@'), usernameLower: function() {
return this.get('username').toLowerCase();
}.property('username'),
userUrl: Discourse.computed.url('usernameLower', '/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'));

View File

@ -18,7 +18,7 @@ Discourse.UserStream = Discourse.Model.extend({
findItems: function() { findItems: function() {
var me = this; var me = this;
if(this.get("loading")) { return; } if(this.get("loading")) { return Ember.RSVP.reject(); }
this.set("loading",true); this.set("loading",true);
var url = Discourse.getURL("/user_actions.json?offset=") + this.get('itemsLoaded') + "&username=" + (this.get('user.username_lower')); var url = Discourse.getURL("/user_actions.json?offset=") + this.get('itemsLoaded') + "&username=" + (this.get('user.username_lower'));

View File

@ -47,5 +47,3 @@ Discourse.FilteredListRoute = Discourse.Route.extend({
Discourse.ListController.filters.forEach(function(filter) { Discourse.ListController.filters.forEach(function(filter) {
Discourse["List" + (filter.capitalize()) + "Route"] = Discourse.FilteredListRoute.extend({ filter: filter }); Discourse["List" + (filter.capitalize()) + "Route"] = Discourse.FilteredListRoute.extend({ filter: filter });
}); });

View File

@ -0,0 +1,119 @@
/**
The common route stuff for a user's preference
@class PreferencesRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
}
});
/**
The route for editing a user's "About Me" bio.
@class PreferencesAboutRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesAboutRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
setupController: function(controller, model) {
controller.setProperties({ model: model, newBio: model.get('bio_raw') });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
},
events: {
changeAbout: function() {
var route = this;
var controller = route.controllerFor('preferencesAbout');
controller.setProperties({ saving: true });
return controller.get('model').save().then(function() {
controller.set('saving', false);
route.transitionTo('user.index');
}, function() {
// model failed to save
controller.set('saving', false);
alert(I18n.t('generic_error'));
});
}
}
});
/**
The route for editing a user's email
@class PreferencesEmailRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesEmailRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
setupController: function(controller, model) {
controller.setProperties({ model: model, newEmail: model.get('email') });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
}
});
/**
The route for updating a user's username
@class PreferencesUsernameRoute
@extends Discourse.RestrictedUserRoute
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({
model: function() {
return this.modelFor('user');
},
renderTemplate: function() {
return this.render({ into: 'user', outlet: 'userOutlet' });
},
// A bit odd, but if we leave to /preferences we need to re-render that outlet
exit: function() {
this._super();
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
},
setupController: function(controller, user) {
controller.setProperties({ model: user, newUsername: user.get('username') });
}
});

View File

@ -1,21 +0,0 @@
/**
This route shows who a user has invited
@class UserInvitedRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserInvitedRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
model: function() {
return Discourse.InviteList.findInvitedBy(this.modelFor('user'));
}
});

View File

@ -1,56 +0,0 @@
/**
Handles routes related to users.
@class UserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserRoute = Discourse.Route.extend({
model: function(params) {
// If we're viewing the currently logged in user, return that object
// instead.
var currentUser = Discourse.User.current();
if (currentUser && (params.username.toLowerCase() === currentUser.get('username_lower'))) {
return currentUser;
}
return Discourse.User.create({username: params.username});
},
afterModel: function() {
return this.modelFor('user').findDetails();
},
serialize: function(params) {
if (!params) return {};
return { username: Em.get(params, 'username').toLowerCase() };
},
setupController: function(controller, user) {
controller.set('model', user);
// Add a search context
this.controllerFor('search').set('searchContext', user.get('searchContext'));
},
activate: function() {
this._super();
var user = this.modelFor('user');
Discourse.MessageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
user.loadUserAction(data);
});
},
deactivate: function() {
this._super();
Discourse.MessageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));
// Remove the search context
this.controllerFor('search').set('searchContext', null);
}
});

View File

@ -0,0 +1,192 @@
/**
Handles routes related to users.
@class UserRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserRoute = Discourse.Route.extend({
model: function(params) {
// If we're viewing the currently logged in user, return that object
// instead.
var currentUser = Discourse.User.current();
if (currentUser && (params.username.toLowerCase() === currentUser.get('username_lower'))) {
return currentUser;
}
return Discourse.User.create({username: params.username});
},
afterModel: function() {
return this.modelFor('user').findDetails();
},
serialize: function(params) {
if (!params) return {};
return { username: Em.get(params, 'username').toLowerCase() };
},
setupController: function(controller, user) {
controller.set('model', user);
// Add a search context
this.controllerFor('search').set('searchContext', user.get('searchContext'));
},
activate: function() {
this._super();
var user = this.modelFor('user');
Discourse.MessageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
user.loadUserAction(data);
});
},
deactivate: function() {
this._super();
Discourse.MessageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));
// Remove the search context
this.controllerFor('search').set('searchContext', null);
}
});
/**
This route shows who a user has invited
@class UserInvitedRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserInvitedRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render({ into: 'user', outlet: 'userOutlet' });
},
model: function() {
return Discourse.InviteList.findInvitedBy(this.modelFor('user'));
}
});
/**
The base route for showing a user's activity
@class UserActivityRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('user_activity', {into: 'user', outlet: 'userOutlet' });
},
model: function() {
return this.modelFor('user');
},
setupController: function(controller, user) {
this.controllerFor('userActivity').set('model', user);
var composerController = this.controllerFor('composer');
controller.set('model', user);
if (Discourse.User.current()) {
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
});
}
});
}
}
});
Discourse.UserPrivateMessagesRoute = Discourse.UserActivityRoute.extend({});
/**
If we request /user/eviltrout without a sub route.
@class UserIndexRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserIndexRoute = Discourse.UserActivityRoute.extend({
redirect: function() {
this.transitionTo('userActivity', this.modelFor('user'));
}
});
/**
The base route for showing an activity stream.
@class UserActivityStreamRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityStreamRoute = Discourse.Route.extend({
model: function() {
return this.modelFor('user').findStream(this.get('userActionType'));
},
renderTemplate: function() {
this.render('user_stream', {into: 'user_activity', outlet: 'activity'});
},
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('user_activity').set('userActionType', this.get('userActionType'));
}
});
// Build all activity stream routes
['bookmarks', 'edits', 'likes_given', 'likes_received', 'replies', 'posts', 'index'].forEach(function (userAction) {
Discourse["UserActivity" + userAction.classify() + "Route"] = Discourse.UserActivityStreamRoute.extend({
userActionType: Discourse.UserAction.TYPES[userAction]
});
});
Discourse.UserPrivateMessagesIndexRoute = Discourse.UserActivityStreamRoute.extend({
userActionType: Discourse.UserAction.TYPES.messages_received
});
Discourse.UserPrivateMessagesSentRoute = Discourse.UserActivityStreamRoute.extend({
userActionType: Discourse.UserAction.TYPES.messages_sent
});
//Discourse.UserTopicsListView = Em.View.extend({ templateName: 'user/topics_list' });
Discourse.UserTopicListRoute = Discourse.Route.extend({
renderTemplate: function() {
this.render('paginated_topic_list', {into: 'user_activity', outlet: 'activity'});
},
setupController: function(controller, model) {
this.controllerFor('user_activity').set('userActionType', this.get('userActionType'));
controller.set('model', model);
}
});
Discourse.UserActivityTopicsRoute = Discourse.UserTopicListRoute.extend({
userActionType: Discourse.UserAction.TYPES.topics,
model: function() {
return Discourse.TopicList.find('topics/created-by/' + this.modelFor('user').get('username_lower'));
}
});
Discourse.UserActivityFavoritesRoute = Discourse.UserTopicListRoute.extend({
userActionType: Discourse.UserAction.TYPES.favorites,
model: function() {
return Discourse.TopicList.find('favorited');
}
});

View File

@ -1,20 +1,18 @@
{{#with view.content}} <div class='row'>
<div class='row'> <div class='topic-meta-data span2'>
<div class='topic-meta-data span2'> <div class='contents'>
<div class='contents'> <div>
<div> <a href='/users/{{unbound username}}'>{{avatar this imageSize="small"}}</a>
<a href='/users/{{unbound username}}'>{{avatar this imageSize="small"}}</a>
</div>
<h5 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h5>
</div> </div>
</div> <h5 {{bindAttr class="staff new_user"}}><a href='{{unbound usernameUrl}}'>{{breakUp username}}</a></h5>
<div class='span11 topic-body'>
<div class="topic-meta-data-inside">
<div class='post-info post-date'>{{unboundAgeWithTooltip created_at}}</div>
{{#if view.previousPost}}<a href='{{unbound url}}' class="post-info arrow" title="{{i18n topic.jump_reply_up}}"><i class='icon icon-arrow-up'></i></a>{{/if}}
</div>
{{{unbound cooked}}}
{{#unless view.previousPost}}<a href='{{unbound url}}' class="arrow" title="{{i18n topic.jump_reply_down}}"><i class='icon icon-arrow-down'></i></a>{{/unless}}
</div> </div>
</div> </div>
{{/with}} <div class='span11 topic-body'>
<div class="topic-meta-data-inside">
<div class='post-info post-date'>{{unboundAgeWithTooltip created_at}}</div>
{{#if view.previousPost}}<a href='{{unbound url}}' class="post-info arrow" title="{{i18n topic.jump_reply_up}}"><i class='icon icon-arrow-up'></i></a>{{/if}}
</div>
{{{unbound cooked}}}
{{#unless view.previousPost}}<a href='{{unbound url}}' class="arrow" title="{{i18n topic.jump_reply_down}}"><i class='icon icon-arrow-down'></i></a>{{/unless}}
</div>
</div>

View File

@ -0,0 +1,44 @@
<div id='list-controls'>
<div class="container">
<ul class="nav nav-pills" id='category-filter'>
{{each availableNavItems itemViewClass="Discourse.NavItemView"}}
</ul>
{{#if canCreateTopic}}
<button class='btn btn-default' {{action createTopic}}><i class='icon icon-plus'></i>{{createTopicText}}</button>
{{/if}}
{{#if canEditCategory}}
<button class='btn btn-default' {{action editCategory category}}>{{i18n category.edit_long}}</button>
{{/if}}
{{#if canCreateCategory}}
<button class='btn btn-default' {{action createCategory}}><i class='icon icon-plus'></i>{{i18n category.create}}</button>
{{/if}}
</div>
</div>
<div class="container">
<div class="row">
<div class="full-width">
<div id='list-area'>
{{#if loading}}
<div class='contents loading'>
<table id='topic-list'>
<tr>
<td colspan='8'>
<div class='spinner'>{{i18n loading}}</div>
</td>
</tr>
</table>
</div>
{{/if}}
{{outlet listView}}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,55 @@
<table id="topic-list">
<tr>
<th>
{{i18n topic.title}}
</th>
<th>{{i18n category_title}}</th>
<th class='num posts'>{{i18n posts}}</th>
<th class='num likes'>{{i18n likes}}</th>
<th class='num views'>{{i18n views}}</th>
<th class='num activity' colspan='2'>{{i18n activity}}</th>
</tr>
{{#group}}
{{#collection contentBinding="view.topics" tagName="tbody" itemTagName="tr"}}
<td class='main-link'>
<a class='title' href="{{unbound lastReadUrl}}">{{{unbound fancy_title}}}</a>
{{#if unread}}
<a href="{{unbound lastReadUrl}}" class='badge unread badge-notification' title='{{i18n topic.unread_posts count="unread"}}'>{{unbound unread}}</a>
{{/if}}
{{#if new_posts}}
<a href="{{unbound lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="new_posts"}}'>{{unbound new_posts}}</a>
{{/if}}
{{#if unseen}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a>
{{/if}}
</td>
<td class='category'>
{{categoryLink category}}
</td>
<td class='num posts'><a href="{{lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td>
<td class='num likes'>
{{#if like_count}}
<a href='{{url}}{{#if has_best_of}}?filter=best_of{{/if}}'>{{like_count}} <i class='icon-heart'></i></a>
{{/if}}
</td>
<td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
{{#if bumped}}
<td class='num activity'>
<a href="{{url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td>
<td class='num activity last'>
<a href="{{lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
</td>
{{else}}
<td class='num activity'>
<a href="{{url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a>
</td>
<td class="activity"></td>
{{/if}}
{{/collection}}
{{/group}}
</table>

View File

@ -5,7 +5,7 @@
</ul> </ul>
{{#if canCreateTopic}} {{#if canCreateTopic}}
<button class='btn btn-default' {{action createTopic}}><i class='icon icon-plus'></i>{{view.createTopicText}}</button> <button class='btn btn-default' {{action createTopic}}><i class='icon icon-plus'></i>{{createTopicText}}</button>
{{/if}} {{/if}}
{{#if canEditCategory}} {{#if canEditCategory}}
@ -21,7 +21,6 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="full-width"> <div class="full-width">
<div id='list-area'> <div id='list-area'>
{{#if loading}} {{#if loading}}
@ -39,7 +38,6 @@
{{outlet listView}} {{outlet listView}}
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,3 @@
{{#with view.content}} <a href='{{unbound url}}'>
<a href='{{unbound url}}'> <span class='badge-category' style="background-color: #{{unbound color}}; color: #{{unbound text_color}};">{{unbound title}}</span>
<span class='badge-category' style="background-color: #{{unbound color}}; color: #{{unbound text_color}};">{{unbound title}}</span> </a>
</a>
{{/with}}

View File

@ -1,6 +1,3 @@
{{#with view.content}} <a href='{{unbound url}}'>
<a href='{{unbound url}}'> {{unbound title}}
{{unbound title}} </a>
</a>
{{/with}}

View File

@ -1,7 +1,4 @@
{{#with view.content}} <a href='{{unbound url}}'>
<a href='{{unbound url}}'> {{avatar this usernamePath="title" imageSize="small"}}
{{avatar this usernamePath="title" imageSize="small"}} {{unbound title}}
{{unbound title}} </a>
</a>
{{/with}}

View File

@ -1,43 +0,0 @@
{{#with view.content}}
{{#group}}
<td class='main-link'>
<a class='title' href="{{unbound lastReadUrl}}">{{{unbound fancy_title}}}</a>
{{#if unread}}
<a href="{{unbound lastReadUrl}}" class='badge unread badge-notification' title='{{i18n topic.unread_posts count="unread"}}'>{{unbound unread}}</a>
{{/if}}
{{#if new_posts}}
<a href="{{unbound lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new_posts count="new_posts"}}'>{{unbound new_posts}}</a>
{{/if}}
{{#if unseen}}
<a href="{{lastReadUrl}}" class='badge new-posts badge-notification' title='{{i18n topic.new}}'><i class='icon icon-asterisk'></i></a>
{{/if}}
</td>
<td class='category'>
{{categoryLink category}}
</td>
<td class='num posts'><a href="{{lastReadUrl}}" class='badge-posts'>{{number posts_count numberKey="posts_long"}}</a></td>
<td class='num likes'>
{{#if like_count}}
<a href='{{url}}{{#if has_best_of}}?filter=best_of{{/if}}'>{{like_count}} <i class='icon-heart'></i></a>
{{/if}}
</td>
<td {{bindAttr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
{{#if bumped}}
<td class='num activity'>
<a href="{{url}}" {{{bindAttr class=":age ageCold"}}} title='{{i18n first_post}}: {{{unboundDate created_at}}}' >{{unboundAge created_at}}</a>
</td>
<td class='num activity last'>
<a href="{{lastPostUrl}}" class='age' title='{{i18n last_post}}: {{{unboundDate bumped_at}}}'>{{unboundAge bumped_at}}</a>
</td>
{{else}}
<td class='num activity'>
<a href="{{url}}" class='age' title='{{i18n first_post}}: {{{unboundDate created_at}}}'>{{unboundAge created_at}}</a>
</td>
<td class="activity"></td>
{{/if}}
{{/group}}
{{/with}}

View File

@ -74,24 +74,9 @@
{{#if details.suggested_topics.length}} {{#if details.suggested_topics.length}}
<div id='suggested-topics'> <div id='suggested-topics'>
<h3>{{i18n suggested_topics.title}}</h3> <h3>{{i18n suggested_topics.title}}</h3>
<div class='topics'> <div class='topics'>
<table id="topic-list"> {{basicTopicList topics=details.suggested_topics}}
<tr>
<th>
{{i18n topic.title}}
</th>
<th>{{i18n category_title}}</th>
<th class='num posts'>{{i18n posts}}</th>
<th class='num likes'>{{i18n likes}}</th>
<th class='num views'>{{i18n views}}</th>
<th class='num activity' colspan='2'>{{i18n activity}}</th>
</tr>
{{each details.suggested_topics itemTagName="tr" itemViewClass="Discourse.SuggestedTopicView"}}
</table>
</div> </div>
<br/> <br/>
<h3>{{{view.browseMoreMessage}}}</h3> <h3>{{{view.browseMoreMessage}}}</h3>

View File

@ -57,6 +57,7 @@
{{/if}} {{/if}}
</div> </div>
<div id='user-activity'>
{{outlet activity}} {{outlet activity}}
</div>

View File

@ -1,24 +1,21 @@
{{#with view.content}} <div {{bindAttr class=":item hidden deleted moderator_action"}}>
<div {{bindAttr class=":item hidden deleted moderator_action"}}> <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> <span class="title">
<span class="title"> <a href="{{unbound postUrl}}">{{unbound title}}</a>
<a href="{{unbound postUrl}}">{{unbound title}}</a> </span>
</span> <span class="type">{{unbound descriptionHtml}}</span>
<span class="type">{{unbound descriptionHtml}}</span>
</div>
<p class='excerpt'>
{{{unbound excerpt}}}
</p>
{{#each children}}
<div class='child-actions'>
<i class="icon {{unbound icon}}"></i>
{{#each items}}
<a href="{{unbound userUrl}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar this imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}</div></a>
{{/each}}
</div>
{{/each}}
</div> </div>
{{/with}} <p class='excerpt'>
{{{unbound excerpt}}}
</p>
{{#each children}}
<div class='child-actions'>
<i class="icon {{unbound icon}}"></i>
{{#each items}}
<a href="{{unbound userUrl}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar this imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}</div></a>
{{/each}}
</div>
{{/each}}
</div>

View File

@ -10,6 +10,11 @@ Discourse.EmbeddedPostView = Discourse.View.extend({
templateName: 'embedded_post', templateName: 'embedded_post',
classNames: ['reply'], classNames: ['reply'],
init: function() {
this._super();
this.set('context', this.get('content'));
},
didInsertElement: function() { didInsertElement: function() {
Discourse.ScreenTrack.instance().track(this.get('elementId'), this.get('post.post_number')); Discourse.ScreenTrack.instance().track(this.get('elementId'), this.get('post.post_number'));
}, },

View File

@ -0,0 +1,12 @@
/**
This view is used for rendering a basic list of topics.
@class BasicTopicListView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
Discourse.BasicTopicListView = Discourse.View.extend({
templateName: 'list/basic_topic_list'
});
Discourse.View.registerHelper('basicTopicList', Discourse.BasicTopicListView);

View File

@ -4,34 +4,24 @@
@class ListTopicsView @class ListTopicsView
@extends Discourse.View @extends Discourse.View
@namespace Discourse @namespace Discourse
@uses Discourse.Scrolling @uses Discourse.LoadMore
@module Discourse @module Discourse
**/ **/
Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, { Discourse.ListTopicsView = Discourse.View.extend(Discourse.LoadMore, {
templateName: 'list/topics', templateName: 'list/topics',
categoryBinding: 'controller.controllers.list.category', categoryBinding: 'controller.controllers.list.category',
canCreateTopicBinding: 'controller.controllers.list.canCreateTopic', canCreateTopicBinding: 'controller.controllers.list.canCreateTopic',
listBinding: 'controller.model', listBinding: 'controller.model',
loadedMore: false, loadedMore: false,
currentTopicId: null, currentTopicId: null,
eyelineSelector: '.topic-list-item',
topicTrackingState: function() { topicTrackingState: function() {
return Discourse.TopicTrackingState.current(); return Discourse.TopicTrackingState.current();
}.property(), }.property(),
willDestroyElement: function() {
this.unbindScrolling();
},
didInsertElement: function() { didInsertElement: function() {
this.bindScrolling(); this._super();
var eyeline = new Discourse.Eyeline('.topic-list-item');
var listTopicsView = this;
eyeline.on('sawBottom', function() {
listTopicsView.loadMore();
});
var scrollPos = Discourse.get('transient.topicListScrollPos'); var scrollPos = Discourse.get('transient.topicListScrollPos');
if (scrollPos) { if (scrollPos) {
Em.run.schedule('afterRender', function() { Em.run.schedule('afterRender', function() {
@ -42,15 +32,10 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
$('html, body').scrollTop(0); $('html, body').scrollTop(0);
}); });
} }
this.set('eyeline', eyeline);
}, },
showTable: function() { hasTopics: Em.computed.gt('list.topics.length', 0),
var topics = this.get('list.topics'); showTable: Em.computed.or('hasTopics', 'topicTrackingState.hasIncoming'),
if(topics) {
return this.get('list.topics').length > 0 || this.get('topicTrackingState.hasIncoming');
}
}.property('list.topics.@each','topicTrackingState.hasIncoming'),
updateTitle: function(){ updateTitle: function(){
Discourse.notifyTitle(this.get('topicTrackingState.incomingCount')); Discourse.notifyTitle(this.get('topicTrackingState.incomingCount'));
@ -76,9 +61,8 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
// When the topic list is scrolled // When the topic list is scrolled
scrolled: function(e) { scrolled: function(e) {
this._super();
this.saveScrollPos(); this.saveScrollPos();
var eyeline = this.get('eyeline');
if (eyeline) { eyeline.update(); }
} }

View File

@ -1,32 +0,0 @@
/**
This view handles the rendering of a list
@class ListView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
Discourse.ListView = Discourse.View.extend({
templateName: 'list/list',
composeViewBinding: Ember.Binding.oneWay('Discourse.composeView'),
// The window has been scrolled
scrolled: function(e) {
var currentView;
currentView = this.get('container.currentView');
return currentView ? typeof currentView.scrolled === "function" ? currentView.scrolled(e) : void 0 : void 0;
},
createTopicText: function() {
if (this.get('controller.category.name')) {
return I18n.t("topic.create_in", {
categoryName: this.get('controller.category.name')
});
} else {
return I18n.t("topic.create");
}
}.property('controller.category.name')
});

View File

@ -0,0 +1,20 @@
/**
This view is used for rendering a basic list of topics.
@class PaginatedTopicListView
@extends Discourse.View
@namespace Discourse
@uses Discourse.LoadMore
@module Discourse
**/
Discourse.PaginatedTopicListView = Discourse.BasicTopicListView.extend(Discourse.LoadMore, {
topics: Em.computed.alias('controller.model.topics'),
classNames: ['paginated-topics-list'],
eyelineSelector: '.paginated-topics-list #topic-list tr',
loadMore: function() {
this.get('controller.model').loadMoreTopics();
}
});

View File

@ -12,9 +12,7 @@ Discourse.TopicListItemView = Discourse.View.extend({
classNameBindings: ['content.archived', ':topic-list-item', 'content.hasExcerpt:has-excerpt'], classNameBindings: ['content.archived', ':topic-list-item', 'content.hasExcerpt:has-excerpt'],
attributeBindings: ['data-topic-id'], attributeBindings: ['data-topic-id'],
'data-topic-id': function() { 'data-topic-id': Em.computed.alias('content.id'),
return this.get('content.id');
}.property('content.id'),
init: function() { init: function() {
this._super(); this._super();

View File

@ -10,17 +10,14 @@ Discourse.SearchResultsTypeView = Ember.CollectionView.extend({
tagName: 'ul', tagName: 'ul',
itemViewClass: Ember.View.extend({ itemViewClass: Ember.View.extend({
tagName: 'li', tagName: 'li',
classNameBindings: ['selectedClass'], classNameBindings: ['selected'],
templateName: Discourse.computed.fmt('parentView.type', "search/%@_result"),
selected: Discourse.computed.propertyEqual('content.index', 'controller.selectedIndex'),
templateName: function() { init: function() {
return "search/" + (this.get('parentView.type')) + "_result"; this._super();
}.property('parentView.type'), this.set('context', this.get('content'));
}
// Is this row currently selected by the keyboard?
selectedClass: function() {
if (this.get('content.index') === this.get('controller.selectedIndex')) return 'selected';
return null;
}.property('controller.selectedIndex')
}) })
}); });

View File

@ -1,13 +0,0 @@
/**
This view is used for rendering a suggested topic
@class SuggestedTopicView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
Discourse.SuggestedTopicView = Discourse.View.extend({
templateName: 'suggested_topic'
});

View File

@ -42,7 +42,7 @@ Discourse.ActivityFilterView = Discourse.View.extend({
url: function() { url: function() {
var section = this.get('content.isPM') ? "/private-messages" : "/activity"; var section = this.get('content.isPM') ? "/private-messages" : "/activity";
return "/users/" + this.get('user.username_lower') + section + this.get('typeKey'); return "/users/" + this.get('user.username_lower') + section + this.get('typeKey');
}.property('typeKey'), }.property('typeKey', 'user.username_lower'),
description: function() { description: function() {
return this.get('content.description') || I18n.t("user.filters.all"); return this.get('content.description') || I18n.t("user.filters.all");

View File

@ -4,45 +4,32 @@
@class UserStreamView @class UserStreamView
@extends Discourse.View @extends Discourse.View
@namespace Discourse @namespace Discourse
@uses Discourse.Scrolling @uses Discourse.LoadMore
@module Discourse @module Discourse
**/ **/
Discourse.UserStreamView = Ember.CollectionView.extend(Discourse.Scrolling, { Discourse.UserStreamView = Ember.CollectionView.extend(Discourse.LoadMore, {
loading: false, loading: false,
elementId: 'user-stream',
content: Em.computed.alias('controller.model.content'), content: Em.computed.alias('controller.model.content'),
itemViewClass: Ember.View.extend({ templateName: 'user/stream_item' }), eyelineSelector: '#user-activity .user-stream .item',
classNames: ['user-stream'],
scrolled: function(e) { itemViewClass: Ember.View.extend({
var eyeline = this.get('eyeline'); templateName: 'user/stream_item',
if (eyeline) { eyeline.update(); } init: function() {
}, this._super();
this.set('context', this.get('content'));
}
}),
loadMore: function() { loadMore: function() {
var userStreamView = this; var userStreamView = this;
if (userStreamView.get('loading')) { return; } if (userStreamView.get('loading')) { return; }
var stream = this.get('stream'); var stream = this.get('controller.model');
stream.findItems().then(function() { stream.findItems().then(function() {
userStreamView.set('loading', false); userStreamView.set('loading', false);
userStreamView.get('eyeline').flushRest(); userStreamView.get('eyeline').flushRest();
}); });
},
willDestroyElement: function() {
this.unbindScrolling();
},
didInsertElement: function() {
this.bindScrolling();
var eyeline = new Discourse.Eyeline('#user-stream .item');
this.set('eyeline', eyeline);
var userStreamView = this;
eyeline.on('sawBottom', function() {
userStreamView.loadMore();
});
} }
}); });

View File

@ -8,6 +8,7 @@
//= require ./pagedown_custom.js //= require ./pagedown_custom.js
// Stuff we need to load first // Stuff we need to load first
//= require ./discourse/mixins/scrolling
//= require_tree ./discourse/mixins //= require_tree ./discourse/mixins
//= require ./discourse/components/computed //= require ./discourse/components/computed
//= require ./discourse/views/view //= require ./discourse/views/view

View File

@ -282,6 +282,11 @@
height: 20px; height: 20px;
} }
#topic-list {
th {
background-color: $topic-list-th-background-color;
}
}
#suggested-topics { #suggested-topics {
margin: 40px 0 40px 20px; margin: 40px 0 40px 20px;
@ -294,9 +299,6 @@
color: darken($darkish_gray, 20%); color: darken($darkish_gray, 20%);
margin-bottom: 10px; margin-bottom: 10px;
} }
th {
background-color: $topic-list-th-background-color;
}
} }
#topic-footer-buttons { #topic-footer-buttons {

View File

@ -237,62 +237,64 @@
} }
} }
#user-stream { #user-activity {
width: 840px; width: 840px;
float: left; float: left;
margin-bottom: 50px; margin-bottom: 50px;
.excerpt { .user-stream {
margin: 5px 0px; .excerpt {
font-size: 13px; margin: 5px 0px;
word-wrap: break-word; font-size: 13px;
color: lighten($black, 30%); word-wrap: break-word;
} color: lighten($black, 30%);
.item.moderator-action { }
background-color: #eef0ff; .item.moderator-action {
} background-color: #eef0ff;
.item.deleted { }
opacity: 0.8; .item.deleted {
background-color: #ffcece; opacity: 0.8;
} background-color: #ffcece;
.item.hidden { }
display: block; .item.hidden {
opacity: 0.4; display: block;
} opacity: 0.4;
.item { }
padding: 10px 8px; .item {
background-color: white; padding: 10px 8px;
border: 1px solid #b9b9b9; background-color: white;
margin-bottom: 10px; border: 1px solid #b9b9b9;
@include border-radius-all(4px); margin-bottom: 10px;
@include box-shadow((0 1px 2px rgba($black, 0.07), inset 0 -4px 4px -4px rgba($black, 0.14))); @include border-radius-all(4px);
} @include box-shadow((0 1px 2px rgba($black, 0.07), inset 0 -4px 4px -4px rgba($black, 0.14)));
.type { }
color: lighten($black, 40%); .type {
} color: lighten($black, 40%);
span.name { }
color: lighten($black, 40%); span.name {
} color: lighten($black, 40%);
.time { }
display: block; .time {
float: right; display: block;
color: silver; float: right;
margin-right: 8px; color: silver;
font-size: 11px; margin-right: 8px;
} font-size: 11px;
.avatar-link { }
float: left; .avatar-link {
margin-right: 10px; float: left;
} margin-right: 10px;
.title { }
display: block; .title {
margin-bottom: 4px; display: block;
font-size: 14px; margin-bottom: 4px;
font-size: 14px;
}
} }
} }
// styling of bottom section // styling of bottom section
#user-stream .child-actions { .user-stream .child-actions {
margin-top: 8px; margin-top: 8px;
.avatar-link { .avatar-link {
float: none; float: none;
@ -312,12 +314,12 @@
} }
@include medium-width { @include medium-width {
#user-stream { #user-activity {
width: 725px; width: 725px;
} }
} }
@include small-width { @include small-width {
#user-stream { #user-activity {
width: 680px; width: 680px;
} }
} }

View File

@ -1,6 +1,6 @@
class ListController < ApplicationController class ListController < ApplicationController
before_filter :ensure_logged_in, except: [:latest, :hot, :category, :category_feed, :latest_feed, :hot_feed] before_filter :ensure_logged_in, except: [:latest, :hot, :category, :category_feed, :latest_feed, :hot_feed, :topics_by]
before_filter :set_category, only: [:category, :category_feed] before_filter :set_category, only: [:category, :category_feed]
skip_before_filter :check_xhr skip_before_filter :check_xhr
@ -28,6 +28,14 @@ class ListController < ApplicationController
end end
end end
def topics_by
list_opts = build_topic_list_options
list = TopicQuery.new(current_user, list_opts).list_topics_by(fetch_user_from_params)
list.more_topics_url = url_for(topics_by_path(list_opts.merge(format: 'json', page: next_page)))
respond(list)
end
def category def category
query = TopicQuery.new(current_user, page: params[:page]) query = TopicQuery.new(current_user, page: params[:page])

View File

@ -201,6 +201,7 @@ Discourse::Application.routes.draw do
post 't' => 'topics#create' post 't' => 'topics#create'
post 'topics/timings' post 'topics/timings'
get 'topics/similar_to' get 'topics/similar_to'
get 'topics/created-by/:username' => 'list#topics_by', as: 'topics_by', constraints: {username: USERNAME_ROUTE_FORMAT}
# Legacy route for old avatars # Legacy route for old avatars
get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', constraints: {topic_id: /\d+/, post_number: /\d+/} get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', constraints: {topic_id: /\d+/, post_number: /\d+/}

View File

@ -8,7 +8,7 @@ task 'integration:create_fixtures' => :environment do
topic: ["/t/280.json"], topic: ["/t/280.json"],
user: ["/users/eviltrout.json", user: ["/users/eviltrout.json",
"/user_actions.json?offset=0&username=eviltrout", "/user_actions.json?offset=0&username=eviltrout",
"/user_actions.json?offset=0&username=eviltrout&filter=4", "/topics/created-by/eviltrout.json",
"/user_actions.json?offset=0&username=eviltrout&filter=5", "/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=6,7,9",
"/user_actions.json?offset=0&username=eviltrout&filter=1", "/user_actions.json?offset=0&username=eviltrout&filter=1",

View File

@ -129,6 +129,14 @@ class TopicQuery
create_list(:posted) {|l| l.where('tu.user_id IS NOT NULL') } create_list(:posted) {|l| l.where('tu.user_id IS NOT NULL') }
end end
def list_topics_by(user)
Rails.logger.info ">>> #{user.id}"
create_list(:user_topics) do |topics|
topics.where(user_id: user.id)
end
end
def list_uncategorized def list_uncategorized
create_list(:uncategorized, unordered: true) do |list| create_list(:uncategorized, unordered: true) do |list|
list = list.where(category_id: nil) list = list.where(category_id: nil)

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

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ test("Activity Streams", function() {
var streamTest = function(url) { var streamTest = function(url) {
visit(url).then(function() { visit(url).then(function() {
ok(exists(".user-heading"), "The heading is rendered"); ok(exists(".user-heading"), "The heading is rendered");
ok(exists("#user-stream"), "The stream is rendered"); ok(exists("#user-activity"), "The activity is rendered");
}); });
}; };