UX: When clicking an activity date, pop up a little menu with options to
go to beginning or end of the topic.
This commit is contained in:
parent
0ce2df36e0
commit
aa41548e8e
17 changed files with 172 additions and 11 deletions
app/assets
javascripts/discourse
components
controllers
routes
templates
views
stylesheets
config/locales
|
@ -32,14 +32,25 @@ export default Ember.Component.extend({
|
||||||
}.property('bumpedAt', 'createdAt'),
|
}.property('bumpedAt', 'createdAt'),
|
||||||
|
|
||||||
title: function() {
|
title: function() {
|
||||||
// return {{i18n last_post}}: {{{raw-date topic.bumped_at}}}
|
|
||||||
return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" +
|
return I18n.t('first_post') + ": " + Discourse.Formatter.longDate(this.get('createdAt')) + "\n" +
|
||||||
I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt'));
|
I18n.t('last_post') + ": " + Discourse.Formatter.longDate(this.get('bumpedAt'));
|
||||||
}.property('bumpedAt', 'createdAt'),
|
}.property('bumpedAt', 'createdAt'),
|
||||||
|
|
||||||
render: function(buffer) {
|
render: function(buffer) {
|
||||||
buffer.push('<a href="' + this.get('topic.lastPostUrl') + '">');
|
|
||||||
buffer.push(Discourse.Formatter.autoUpdatingRelativeAge(this.get('bumpedAt')));
|
buffer.push(Discourse.Formatter.autoUpdatingRelativeAge(this.get('bumpedAt')));
|
||||||
buffer.push("</a>");
|
},
|
||||||
|
|
||||||
|
click: function() {
|
||||||
|
var topic = this.get('topic');
|
||||||
|
|
||||||
|
if (Discourse.Mobile.mobileView) {
|
||||||
|
Discourse.URL.routeTo(topic.get('lastPostUrl'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendAction('action', {
|
||||||
|
topic: topic,
|
||||||
|
position: this.$('span').position()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,12 @@ export default Ember.Component.extend({
|
||||||
// Without a topic list, we assume it's loaded always.
|
// Without a topic list, we assume it's loaded always.
|
||||||
this.set('loaded', true);
|
this.set('loaded', true);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
clickedActivity: function(data) {
|
||||||
|
this.sendAction('activityAction', data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
function entranceDate(dt) {
|
||||||
|
var bumpedAt = new Date(dt),
|
||||||
|
today = new Date();
|
||||||
|
|
||||||
|
if (bumpedAt.getDate() === today.getDate()) {
|
||||||
|
return moment(bumpedAt).format(I18n.t("dates.time"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bumpedAt.getYear() === today.getYear()) {
|
||||||
|
return moment(bumpedAt).format(I18n.t("dates.long_no_year"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return moment(bumpedAt).format(I18n.t('dates.long_with_year'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ember.ObjectController.extend({
|
||||||
|
position: null,
|
||||||
|
|
||||||
|
topDate: function() {
|
||||||
|
return entranceDate(this.get('created_at'));
|
||||||
|
}.property('model.created_at'),
|
||||||
|
|
||||||
|
bottomDate: function() {
|
||||||
|
return entranceDate(this.get('bumped_at'));
|
||||||
|
}.property('model.bumped_at'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
show: function(data) {
|
||||||
|
// Show the chooser but only if the model changes
|
||||||
|
if (this.get('model') !== data.topic) {
|
||||||
|
this.set('model', data.topic);
|
||||||
|
this.set('position', data.position);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enterTop: function() {
|
||||||
|
Discourse.URL.routeTo(this.get('url'));
|
||||||
|
},
|
||||||
|
|
||||||
|
enterBottom: function() {
|
||||||
|
Discourse.URL.routeTo(this.get('lastPostUrl'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,6 +1,10 @@
|
||||||
var ApplicationRoute = Em.Route.extend({
|
var ApplicationRoute = Em.Route.extend({
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
showTopicEntrance: function(data) {
|
||||||
|
this.controllerFor('topic-entrance').send('show', data);
|
||||||
|
},
|
||||||
|
|
||||||
error: function(err, transition) {
|
error: function(err, transition) {
|
||||||
if (err.status === 404) {
|
if (err.status === 404) {
|
||||||
// 404
|
// 404
|
||||||
|
|
|
@ -77,6 +77,10 @@ Discourse.Route.reopenClass({
|
||||||
$('#discourse-modal').modal('hide');
|
$('#discourse-modal').modal('hide');
|
||||||
var hideDropDownFunction = $('html').data('hide-dropdown');
|
var hideDropDownFunction = $('html').data('hide-dropdown');
|
||||||
if (hideDropDownFunction) { hideDropDownFunction(); }
|
if (hideDropDownFunction) { hideDropDownFunction(); }
|
||||||
|
|
||||||
|
// TODO: Avoid container lookup here
|
||||||
|
var appEvents = Discourse.__container__.lookup('app-events:main');
|
||||||
|
appEvents.trigger('dom:clean');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{render "modal"}}
|
{{render "modal"}}
|
||||||
|
{{render "topic-entrance"}}
|
||||||
{{render "composer"}}
|
{{render "composer"}}
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
{{number topic.views numberKey="views_long"}}
|
{{number topic.views numberKey="views_long"}}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{{activity-column topic=topic class="num"}}
|
{{activity-column topic=topic class="num" action="clickedActivity"}}
|
||||||
</tr>
|
</tr>
|
||||||
{{/grouped-each}}
|
{{/grouped-each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -5,28 +5,28 @@
|
||||||
{{#if content.yearly}}
|
{{#if content.yearly}}
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_year}}</h2>
|
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_year}}</h2>
|
||||||
{{basic-topic-list topicList=content.yearly hideCategory=hideCategory}}
|
{{basic-topic-list topicList=content.yearly hideCategory=hideCategory activityAction="showTopicEntrance"}}
|
||||||
{{#if content.yearly.topics.length}}<a href="{{unbound showMoreYearlyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
{{#if content.yearly.topics.length}}<a href="{{unbound showMoreYearlyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if content.monthly}}
|
{{#if content.monthly}}
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_month}}</h2>
|
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_month}}</h2>
|
||||||
{{basic-topic-list topicList=content.monthly hideCategory=hideCategory}}
|
{{basic-topic-list topicList=content.monthly hideCategory=hideCategory activityAction="showTopicEntrance"}}
|
||||||
{{#if content.monthly.topics.length}}<a href="{{unbound showMoreMonthlyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
{{#if content.monthly.topics.length}}<a href="{{unbound showMoreMonthlyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if content.weekly}}
|
{{#if content.weekly}}
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_week}}</h2>
|
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.this_week}}</h2>
|
||||||
{{basic-topic-list topicList=content.weekly hideCategory=hideCategory}}
|
{{basic-topic-list topicList=content.weekly hideCategory=hideCategory activityAction="showTopicEntrance"}}
|
||||||
{{#if content.weekly.topics.length}}<a href="{{unbound showMoreWeeklyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
{{#if content.weekly.topics.length}}<a href="{{unbound showMoreWeeklyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if content.daily}}
|
{{#if content.daily}}
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.today}}</h2>
|
<h2><i class="fa fa-calendar-o"></i> {{i18n filters.top.today}}</h2>
|
||||||
{{basic-topic-list topicList=content.daily hideCategory=hideCategory}}
|
{{basic-topic-list topicList=content.daily hideCategory=hideCategory activityAction="showTopicEntrance"}}
|
||||||
{{#if content.daily.topics.length}}<a href="{{unbound showMoreDailyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
{{#if content.daily.topics.length}}<a href="{{unbound showMoreDailyUrl}}" class='btn btn-default pull-right'>{{i18n show_more}}</a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -49,4 +49,4 @@
|
||||||
{{posts-count-column topic=model class="num"}}
|
{{posts-count-column topic=model class="num"}}
|
||||||
<td {{bind-attr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
|
<td {{bind-attr class=":num :views viewsHeat"}}>{{number views numberKey="views_long"}}</td>
|
||||||
|
|
||||||
{{activity-column topic=model class="num"}}
|
{{activity-column topic=model class="num" action="showTopicEntrance"}}
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
{{basic-topic-list topicList=model hideCategory=hideCategory showParticipants=showParticipants}}
|
{{basic-topic-list topicList=model
|
||||||
|
hideCategory=hideCategory
|
||||||
|
showParticipants=showParticipants
|
||||||
|
activityAction="showTopicEntrance"}}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<button {{action enterTop}} class='btn full no-text'>
|
||||||
|
<i class='fa fa-caret-up'></i> {{topDate}}
|
||||||
|
</button>
|
||||||
|
<button {{action enterBottom}} class='btn full no-text jump-bottom'>
|
||||||
|
<i class='fa fa-caret-down'></i> {{bottomDate}}
|
||||||
|
</button>
|
|
@ -102,7 +102,7 @@
|
||||||
<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'>
|
||||||
{{basic-topic-list topics=details.suggested_topics}}
|
{{basic-topic-list topics=details.suggested_topics activityAction="showTopicEntrance"}}
|
||||||
</div>
|
</div>
|
||||||
<h3>{{{view.browseMoreMessage}}}</h3>
|
<h3>{{{view.browseMoreMessage}}}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
50
app/assets/javascripts/discourse/views/topic-entrance.js.es6
Normal file
50
app/assets/javascripts/discourse/views/topic-entrance.js.es6
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
export default Ember.View.extend({
|
||||||
|
elementId: 'topic-entrance',
|
||||||
|
classNameBindings: ['visible::hidden'],
|
||||||
|
visible: Em.computed.notEmpty('controller.model'),
|
||||||
|
|
||||||
|
_positionChanged: function() {
|
||||||
|
var pos = this.get('controller.position');
|
||||||
|
if (!pos) { return; }
|
||||||
|
|
||||||
|
var $self = this.$();
|
||||||
|
|
||||||
|
// Move after we render so the height is correct
|
||||||
|
Em.run.schedule('afterRender', function() {
|
||||||
|
var width = $self.width(),
|
||||||
|
height = $self.height();
|
||||||
|
pos.left = (parseInt(pos.left) - (width / 2));
|
||||||
|
pos.top = (parseInt(pos.top) - (height / 2));
|
||||||
|
|
||||||
|
var windowWidth = $(window).width();
|
||||||
|
if (pos.left + width > windowWidth) {
|
||||||
|
pos.left = (windowWidth - width) - 5;
|
||||||
|
}
|
||||||
|
$self.css(pos);
|
||||||
|
});
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
$('html').off('mousedown.topic-entrance').on('mousedown.topic-entrance', function(e) {
|
||||||
|
var $target = $(e.target);
|
||||||
|
if (($target.prop('id') === 'topic-entrance') || ($self.has($target).length !== 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._cleanUp();
|
||||||
|
});
|
||||||
|
}.observes('controller.position'),
|
||||||
|
|
||||||
|
_removed: function() {
|
||||||
|
$('html').off('mousedown.topic-entrance');
|
||||||
|
this.appEvents.off('dom:clean', this, '_cleanUp');
|
||||||
|
}.on('willDestroyElement'),
|
||||||
|
|
||||||
|
_cleanUp: function() {
|
||||||
|
this.set('controller.model', null);
|
||||||
|
$('html').off('mousedown.topic-entrance');
|
||||||
|
},
|
||||||
|
|
||||||
|
_wireClean: function() {
|
||||||
|
this.appEvents.on('dom:clean', this, '_cleanUp');
|
||||||
|
}.on('didInsertElement'),
|
||||||
|
|
||||||
|
});
|
|
@ -15,6 +15,7 @@
|
||||||
@import "desktop/topic";
|
@import "desktop/topic";
|
||||||
@import "desktop/upload";
|
@import "desktop/upload";
|
||||||
@import "desktop/user";
|
@import "desktop/user";
|
||||||
|
@import "desktop/topic-entrance";
|
||||||
|
|
||||||
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */
|
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */
|
||||||
|
|
||||||
|
|
25
app/assets/stylesheets/desktop/topic-entrance.scss
Normal file
25
app/assets/stylesheets/desktop/topic-entrance.scss
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#topic-entrance {
|
||||||
|
border: 1px solid scale-color-diff();
|
||||||
|
padding: 5px;
|
||||||
|
background: $secondary;
|
||||||
|
@include box-shadow(0 0px 2px rgba(0,0,0, .2));
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 133px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
button.full {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
i {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.btn.jump-bottom {
|
||||||
|
margin: 5px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -138,6 +138,9 @@
|
||||||
}
|
}
|
||||||
.activity {
|
.activity {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.age {
|
.age {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
|
|
@ -30,6 +30,9 @@ en:
|
||||||
mb: MB
|
mb: MB
|
||||||
tb: TB
|
tb: TB
|
||||||
dates:
|
dates:
|
||||||
|
time: "h:mm a"
|
||||||
|
long_no_year: "MMM DD h:mm a"
|
||||||
|
long_with_year: "MMM DD, YYYY h:mm a"
|
||||||
tiny:
|
tiny:
|
||||||
half_a_minute: "< 1m"
|
half_a_minute: "< 1m"
|
||||||
less_than_x_seconds:
|
less_than_x_seconds:
|
||||||
|
|
Reference in a new issue