FIX: /categories page issues

FIX: endless spinner when /categories is set to homepage and you click the home logo
FIX: latest column should respect topic state for the current user (new, unread, etc.)
FIX: post count should have heat colors applied based on like ratios
FIX: Add "More" button at the bottom of the latest column
UX: The topic count number in the categories panel should be slightly larger
This commit is contained in:
Régis Hanol 2016-08-18 19:41:21 +02:00
parent 78e8aa823d
commit 96b6d342cc
9 changed files with 134 additions and 105 deletions

View file

@ -1,3 +1,4 @@
import computed from 'ember-addons/ember-computed-decorators';
import DiscoveryController from 'discourse/controllers/discovery';
export default DiscoveryController.extend({
@ -9,36 +10,14 @@ export default DiscoveryController.extend({
// this makes sure the composer isn't scoping to a specific category
category: null,
actions: {
refresh() {
// Don't refresh if we're still loading
if (this.get('controllers.discovery.loading')) { return; }
// If we `send('loading')` here, due to returning true it bubbles up to the
// router and ember throws an error due to missing `handlerInfos`.
// Lesson learned: Don't call `loading` yourself.
this.set('controllers.discovery.loading', true);
const CategoryList = require('discourse/models/category-list').default;
const parentCategory = this.get('model.parentCategory');
const promise = parentCategory ? CategoryList.listForParent(this.store, parentCategory) :
CategoryList.list(this.store);
const self = this;
promise.then(function(list) {
self.set('model', list);
self.send('loadingComplete');
});
}
@computed
canEdit() {
return Discourse.User.currentProp('staff');
},
canEdit: function() {
return Discourse.User.currentProp('staff');
}.property(),
latestTopicOnly: function() {
return this.get('model.categories').find(c => c.get('featuredTopics.length') > 1) === undefined;
}.property('model.categories.@each.featuredTopics.length')
@computed("model.categories.@each.featuredTopics.length")
latestTopicOnly() {
return this.get("model.categories").find(c => c.get('featuredTopics.length') > 1) === undefined;
}
});

View file

@ -154,15 +154,15 @@ const DiscourseURL = Ember.Object.extend({
// Schedule a DOM cleanup event
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM');
// TODO: Extract into rules we can inject into the URL handler
if (this.navigatedToHome(oldPath, path, opts)) { return; }
if (oldPath === path) {
// If navigating to the same path send an app event. Views can watch it
// and tell their controllers to refresh
this.appEvents.trigger('url:refresh');
}
// TODO: Extract into rules we can inject into the URL handler
if (this.navigatedToHome(oldPath, path, opts)) { return; }
return this.handleURL(path, opts);
},

View file

@ -71,11 +71,6 @@ const Topic = RestModel.extend({
I18n.t('last_post') + ": " + longDate(this.get('bumpedAt'));
}.property('bumpedAt'),
@computed('replyCount')
replyTitle(count) {
return I18n.t("posts_likes", { count });
},
createdAt: function() {
return new Date(this.get('created_at'));
}.property('created_at'),

View file

@ -31,7 +31,11 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
},
setupController(controller, model) {
TopicList.find("latest").then(result => model.set("topicList", result));
model.set("loadingTopics", true);
TopicList.find("latest")
.then(result => model.set("topicList", result))
.finally(() => model.set("loadingTopics", false));
controller.set("model", model);
@ -44,6 +48,28 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
},
actions: {
refresh() {
const controller = this.controllerFor("discovery/categories");
// Don't refresh if we're still loading
if (!controller || controller.get("loading")) { return; }
// If we `send('loading')` here, due to returning true it bubbles up to the
// router and ember throws an error due to missing `handlerInfos`.
// Lesson learned: Don't call `loading` yourself.
controller.set("loading", true);
const parentCategory = this.get("model.parentCategory");
const promise = parentCategory ? CategoryList.listForParent(this.store, parentCategory) :
CategoryList.list(this.store);
promise.then(list => {
this.setupController(controller, list);
controller.send("loadingComplete");
});
},
createCategory() {
const groups = this.site.groups,
everyoneName = groups.findBy("id", 0).name;

View file

@ -47,50 +47,61 @@
</tr>
</thead>
<tbody>
{{#each model.topicList.topics as |t|}}
<tr>
<table>
<tbody>
<tr class="{{if t.archived 'archived'}}" data-topic-id={{unbound t.id}}>
<td class="topic-poster">
{{#with t.posters.lastObject.user as |lastPoster|}}
{{#user-link user=lastPoster}}
{{avatar lastPoster imageSize="large"}}
{{/user-link}}
{{/with}}
</td>
<td class="main-link">
<tr>
{{topic-status topic=t}}
{{topic-link t}}
{{#if t.unseen}}
<span class="badge-notification new-topic"></span>
{{/if}}
</tr>
<tr>
{{category-link t.category}}
{{#if t.tags}}
{{#each t.visibleListTags as |tag|}}
{{discourse-tag tag}}
{{/each}}
{{/if}}
</tr>
</td>
<td class="topic-stats">
<div class="topic-replies">
<a href="{{t.lastPostUrl}}" title="{{t.replyTitle}}">{{number t.replyCount noTitle="true"}}</a>
</div>
<div class="topic-last-activity">
<a href="{{t.lastPostUrl}}" title="{{t.bumpedAtTitle}}">{{format-date t.bumpedAt format="tiny" noTitle="true"}}</a>
</div>
</td>
{{#if model.loadingTopics}}
{{loading-spinner}}
{{else}}
{{#if model.topicList.topics}}
{{#each model.topicList.topics as |t|}}
<tr>
<table>
<tbody>
<tr class="{{if t.archived 'archived'}}" data-topic-id={{unbound t.id}}>
<td class="topic-poster">
{{#with t.posters.firstObject.user as |originalPoster|}}
{{#user-link user=originalPoster}}
{{avatar originalPoster imageSize="large"}}
{{/user-link}}
{{/with}}
</td>
<td class="main-link">
<tr>
{{topic-status topic=t}}
{{topic-link t}}
{{topic-post-badges newPosts=t.totalUnread unseen=t.unseen url=t.lastUnreadUrl}}
</tr>
<tr>
{{category-link t.category}}
{{#if t.tags}}
{{#each t.visibleListTags as |tag|}}
{{discourse-tag tag}}
{{/each}}
{{/if}}
</tr>
</td>
<td class="topic-stats">
{{raw "list/posts-count-column" topic=t tagName="div"}}
<div class="topic-last-activity">
<a href="{{t.lastPostUrl}}" title="{{t.bumpedAtTitle}}">{{format-date t.bumpedAt format="tiny" noTitle="true"}}</a>
</div>
</td>
</tr>
</tbody>
</table>
</tr>
</tbody>
</table>
</tr>
{{else}}
{{loading-spinner}}
{{/each}}
{{/each}}
<tr class="more-topics">
<td>
<a href="/latest" class="btn pull-right">{{i18n "more"}}</a>
</td>
</tr>
{{else}}
<tr class="no-topics">
<td>
<h3>{{i18n "topics.none.latest"}}</h3>
</td>
</tr>
{{/if}}
{{/if}}
</tbody>
</table>
<div class="clearfix"></div>

View file

@ -1,3 +1,3 @@
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}}' title='{{view.title}}'>
<a href class='posts-map badge-posts {{view.likesHeat}}'>{{number topic.replyCount}}</a>
<a href class='posts-map badge-posts {{view.likesHeat}}'>{{number topic.replyCount noTitle="true"}}</a>
</{{view.tagName}}>

View file

@ -1,32 +1,32 @@
import computed from 'ember-addons/ember-computed-decorators';
import { fmt } from 'discourse/lib/computed';
export default Ember.Object.extend({
tagName: "td",
ratio: function() {
var likes = parseFloat(this.get('topic.like_count')),
posts = parseFloat(this.get('topic.posts_count'));
@computed("topic.like_count", "topic.posts_count")
ratio(likeCount, postCount) {
const likes = parseFloat(likeCount);
const posts = parseFloat(postCount);
if (posts < 10) { return 0; }
return (likes || 0) / posts;
}.property(),
},
title: function() {
return I18n.messageFormat('posts_likes_MF', {
count: this.get('topic.replyCount'),
ratio: this.get('ratioText')
}).trim();
}.property(),
@computed("topic.replyCount", "ratioText")
title(count, ratio) {
return I18n.messageFormat('posts_likes_MF', { count, ratio }).trim();
},
ratioText: function() {
var ratio = this.get('ratio');
var settings = Discourse.SiteSettings;
if (ratio > settings.topic_post_like_heat_high) { return 'high'; }
@computed("ratio")
ratioText(ratio) {
const settings = this.siteSettings;
if (ratio > settings.topic_post_like_heat_high) { return 'high'; }
if (ratio > settings.topic_post_like_heat_medium) { return 'med'; }
if (ratio > settings.topic_post_like_heat_low) { return 'low'; }
if (ratio > settings.topic_post_like_heat_low) { return 'low'; }
return '';
}.property(),
},
likesHeat: fmt('ratioText', 'heatmap-%@'),
});

View file

@ -124,12 +124,24 @@ html.anon .topic-list a.title:visited:not(.badge-notification) {color: dark-ligh
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 40%));
}
}
.topic-replies {
font-weight: bold;
.num.posts {
text-align: right;
a {
padding: 0;
}
}
.posts-map.posts {
margin-bottom: 10px;
width: 100%;
.badge-posts {
font-weight: bold;
}
}
.topic-list-latest {
margin-left: 4%;
.new-posts {
margin-left: 5px;
}
}
.topic-list.categories {
th.stats {
@ -138,6 +150,16 @@ html.anon .topic-list a.title:visited:not(.badge-notification) {color: dark-ligh
.stats {
vertical-align: top;
text-align: center;
.value {
font-size: 1.2em;
}
}
}
.no-topics, .more-topics {
border-bottom: none;
td {
padding-right: 0 !important;
color: $primary;
}
}
}

View file

@ -1936,10 +1936,6 @@ en:
posts: "Posts"
posts_long: "there are {{number}} posts in this topic"
posts_likes:
one: "This topic has 1 reply."
other: "This topic has {{count}} replies."
# keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
posts_likes_MF: |
This topic has {count, plural, one {1 reply} other {# replies}} {ratio, select,