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'; import DiscoveryController from 'discourse/controllers/discovery';
export default DiscoveryController.extend({ export default DiscoveryController.extend({
@ -9,36 +10,14 @@ export default DiscoveryController.extend({
// this makes sure the composer isn't scoping to a specific category // this makes sure the composer isn't scoping to a specific category
category: null, category: null,
actions: { @computed
canEdit() {
refresh() { return Discourse.User.currentProp('staff');
// 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');
});
}
}, },
canEdit: function() { @computed("model.categories.@each.featuredTopics.length")
return Discourse.User.currentProp('staff'); latestTopicOnly() {
}.property(), return this.get("model.categories").find(c => c.get('featuredTopics.length') > 1) === undefined;
}
latestTopicOnly: function() {
return this.get('model.categories').find(c => c.get('featuredTopics.length') > 1) === undefined;
}.property('model.categories.@each.featuredTopics.length')
}); });

View file

@ -154,15 +154,15 @@ const DiscourseURL = Ember.Object.extend({
// Schedule a DOM cleanup event // Schedule a DOM cleanup event
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM'); 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 (oldPath === path) {
// If navigating to the same path send an app event. Views can watch it // If navigating to the same path send an app event. Views can watch it
// and tell their controllers to refresh // and tell their controllers to refresh
this.appEvents.trigger('url: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); return this.handleURL(path, opts);
}, },

View file

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

View file

@ -31,7 +31,11 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
}, },
setupController(controller, model) { 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); controller.set("model", model);
@ -44,6 +48,28 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
}, },
actions: { 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() { createCategory() {
const groups = this.site.groups, const groups = this.site.groups,
everyoneName = groups.findBy("id", 0).name; everyoneName = groups.findBy("id", 0).name;

View file

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

View file

@ -1,3 +1,3 @@
<{{view.tagName}} class='num posts-map posts {{view.likesHeat}}' title='{{view.title}}'> <{{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.tagName}}>

View file

@ -1,32 +1,32 @@
import computed from 'ember-addons/ember-computed-decorators';
import { fmt } from 'discourse/lib/computed'; import { fmt } from 'discourse/lib/computed';
export default Ember.Object.extend({ export default Ember.Object.extend({
tagName: "td", tagName: "td",
ratio: function() {
var likes = parseFloat(this.get('topic.like_count')), @computed("topic.like_count", "topic.posts_count")
posts = parseFloat(this.get('topic.posts_count')); ratio(likeCount, postCount) {
const likes = parseFloat(likeCount);
const posts = parseFloat(postCount);
if (posts < 10) { return 0; } if (posts < 10) { return 0; }
return (likes || 0) / posts; return (likes || 0) / posts;
}.property(), },
title: function() { @computed("topic.replyCount", "ratioText")
return I18n.messageFormat('posts_likes_MF', { title(count, ratio) {
count: this.get('topic.replyCount'), return I18n.messageFormat('posts_likes_MF', { count, ratio }).trim();
ratio: this.get('ratioText') },
}).trim();
}.property(),
ratioText: function() { @computed("ratio")
var ratio = this.get('ratio'); ratioText(ratio) {
const settings = this.siteSettings;
var settings = Discourse.SiteSettings; if (ratio > settings.topic_post_like_heat_high) { return 'high'; }
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_medium) { return 'med'; }
if (ratio > settings.topic_post_like_heat_low) { return 'low'; } if (ratio > settings.topic_post_like_heat_low) { return 'low'; }
return ''; return '';
}.property(), },
likesHeat: fmt('ratioText', 'heatmap-%@'), 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%)); color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 40%));
} }
} }
.topic-replies { .num.posts {
font-weight: bold; text-align: right;
a {
padding: 0;
}
}
.posts-map.posts {
margin-bottom: 10px; margin-bottom: 10px;
width: 100%;
.badge-posts {
font-weight: bold;
}
} }
.topic-list-latest { .topic-list-latest {
margin-left: 4%; margin-left: 4%;
.new-posts {
margin-left: 5px;
}
} }
.topic-list.categories { .topic-list.categories {
th.stats { th.stats {
@ -138,6 +150,16 @@ html.anon .topic-list a.title:visited:not(.badge-notification) {color: dark-ligh
.stats { .stats {
vertical-align: top; vertical-align: top;
text-align: center; 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: "Posts"
posts_long: "there are {{number}} posts in this topic" 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 # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
posts_likes_MF: | posts_likes_MF: |
This topic has {count, plural, one {1 reply} other {# replies}} {ratio, select, This topic has {count, plural, one {1 reply} other {# replies}} {ratio, select,