diff --git a/app/assets/javascripts/discourse/components/time-gap.js.es6 b/app/assets/javascripts/discourse/components/time-gap.js.es6
new file mode 100644
index 000000000..fb42dc324
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/time-gap.js.es6
@@ -0,0 +1,20 @@
+export default Ember.Component.extend({
+ classNameBindings: [':time-gap'],
+
+ render(buffer) {
+ const gapDays = this.get('gapDays');
+
+ let timeGapWords;
+ if (gapDays < 30) {
+ timeGapWords = I18n.t('dates.later.x_days', {count: gapDays});
+ } else if (gapDays < 365) {
+ const gapMonths = Math.floor(gapDays / 30);
+ timeGapWords = I18n.t('dates.later.x_months', {count: gapMonths});
+ } else {
+ const gapYears = Math.floor(gapDays / 365);
+ timeGapWords = I18n.t('dates.later.x_years', {count: gapYears});
+ }
+
+ buffer.push("
" + timeGapWords + "
");
+ }
+});
diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6
index 61a70f5d2..e92f92f41 100644
--- a/app/assets/javascripts/discourse/models/post-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/post-stream.js.es6
@@ -1,5 +1,21 @@
import RestModel from 'discourse/models/rest';
+function calcDayDiff(p1, p2) {
+ if (!p1) { return; }
+
+ const date = p1.get('created_at');
+ if (date) {
+ if (p2) {
+ const lastDate = p2.get('created_at');
+ if (lastDate) {
+ const delta = new Date(date).getTime() - new Date(lastDate).getTime();
+ const days = Math.round(delta / (1000 * 60 * 60 * 24));
+
+ p1.set('daysSincePrevious', days);
+ }
+ }
+ }
+}
const PostStream = RestModel.extend({
loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
notLoading: Em.computed.not('loading'),
@@ -367,14 +383,22 @@ const PostStream = RestModel.extend({
},
prependPost(post) {
- this.get('posts').unshiftObject(this.storePost(post));
+ const stored = this.storePost(post);
+ if (stored) {
+ const posts = this.get('posts');
+ calcDayDiff(posts.get('firstObject'), stored);
+ posts.unshiftObject(stored);
+ }
+
return post;
},
appendPost(post) {
const stored = this.storePost(post);
if (stored) {
- this.get('posts').addObject(stored);
+ const posts = this.get('posts');
+ calcDayDiff(stored, posts.get('lastObject'));
+ posts.addObject(stored);
}
return post;
},
@@ -627,7 +651,7 @@ const PostStream = RestModel.extend({
const postId = Em.get(post, 'id');
if (postId) {
const postIdentityMap = this.get('postIdentityMap'),
- existing = postIdentityMap.get(post.get('id'));
+ existing = postIdentityMap.get(post.get('id'));
if (existing) {
// If the post is in the identity map, update it and return the old reference.
diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6
index ba3744ec0..9e720b36d 100644
--- a/app/assets/javascripts/discourse/models/post.js.es6
+++ b/app/assets/javascripts/discourse/models/post.js.es6
@@ -27,6 +27,10 @@ const Post = RestModel.extend({
notDeleted: Em.computed.not('deleted'),
userDeleted: Em.computed.empty('user_id'),
+ hasTimeGap: function() {
+ return (this.get('daysSincePrevious') || 0) > Discourse.SiteSettings.show_time_gap_days;
+ }.property('daysSincePrevious'),
+
showName: function() {
const name = this.get('name');
return name && (name !== this.get('username')) && Discourse.SiteSettings.display_name_on_posts;
diff --git a/app/assets/javascripts/discourse/templates/components/time-gap.hbs b/app/assets/javascripts/discourse/templates/components/time-gap.hbs
new file mode 100644
index 000000000..1667ed07c
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/time-gap.hbs
@@ -0,0 +1,5 @@
+
diff --git a/app/assets/javascripts/discourse/templates/post.hbs b/app/assets/javascripts/discourse/templates/post.hbs
index bbcd12ace..60750cc29 100644
--- a/app/assets/javascripts/discourse/templates/post.hbs
+++ b/app/assets/javascripts/discourse/templates/post.hbs
@@ -1,5 +1,9 @@
{{post-gap post=this postStream=controller.model.postStream before="true"}}
+{{#if hasTimeGap}}
+ {{time-gap gapDays=daysSincePrevious}}
+{{/if}}
+
{{view 'reply-history' content=replyHistory}}
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index dedd9735c..6b6eb254e 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -717,6 +717,21 @@ $topic-avatar-width: 45px;
width: calc(#{$topic-avatar-width} + #{$topic-body-width} + 2 * #{$topic-body-width-padding});
}
+.time-gap {
+ width: 755px;
+ border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
+}
+.time-gap-words {
+ display: inline-block;
+ padding: 0.5em 1em;
+ margin: 0.5em 0 0.5em 56px;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 0.8em;
+ background-color: dark-light-diff($primary, $secondary, 90%, -60%);
+ color: lighten($primary, 30%);
+}
+
.posts-wrapper {
position: relative;
-webkit-font-smoothing: subpixel-antialiased;
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 501c5e589..fa4c6fbcf 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -92,6 +92,16 @@ en:
x_days:
one: "1 day ago"
other: "%{count} days ago"
+ later:
+ x_days:
+ one: "1 day layer"
+ other: "%{count} days later"
+ x_months:
+ one: "1 month layer"
+ other: "%{count} months later"
+ x_years:
+ one: "1 year layer"
+ other: "%{count} years later"
share:
topic: 'share a link to this topic'
post: 'post #%{postNumber}'
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 27ac0af5e..a90c20be9 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1120,6 +1120,7 @@ en:
full_name_required: "Full name is a required field of a user's profile."
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
+ show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic."
invites_per_page: "Default invites shown on the user page."
short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value."
default_code_lang: "Default programming language syntax highlighting applied to GitHub code blocks (lang-auto, ruby, python etc.)"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 44c4fe249..792b824ed 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -415,6 +415,9 @@ posting:
display_name_on_posts:
client: true
default: false
+ show_time_gap_days:
+ default: 4
+ client: true
short_progress_text_threshold:
client: true
default: 10000
diff --git a/test/javascripts/models/post-stream-test.js.es6 b/test/javascripts/models/post-stream-test.js.es6
index a510c4839..74569a036 100644
--- a/test/javascripts/models/post-stream-test.js.es6
+++ b/test/javascripts/models/post-stream-test.js.es6
@@ -26,6 +26,40 @@ test('defaults', function() {
present(postStream.get('topic'));
});
+test('daysSincePrevious when appending', function(assert) {
+ const postStream = buildStream(10000001, [1,2,3]);
+ const store = postStream.store;
+
+ const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}),
+ p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}),
+ p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"});
+
+ postStream.appendPost(p1);
+ postStream.appendPost(p2);
+ postStream.appendPost(p3);
+
+ assert.ok(!p1.get('daysSincePrevious'));
+ assert.equal(p2.get('daysSincePrevious'), 2);
+ assert.equal(p3.get('daysSincePrevious'), 1);
+});
+
+test('daysSincePrevious when prepending', function(assert) {
+ const postStream = buildStream(10000001, [1,2,3]);
+ const store = postStream.store;
+
+ const p1 = store.createRecord('post', {id: 1, post_number: 1, created_at: "2015-05-29T18:17:35.868Z"}),
+ p2 = store.createRecord('post', {id: 2, post_number: 2, created_at: "2015-06-01T01:07:25.761Z"}),
+ p3 = store.createRecord('post', {id: 3, post_number: 3, created_at: "2015-06-02T01:07:25.761Z"});
+
+ postStream.prependPost(p3);
+ postStream.prependPost(p2);
+ postStream.prependPost(p1);
+
+ assert.ok(!p1.get('daysSincePrevious'));
+ assert.equal(p2.get('daysSincePrevious'), 2);
+ assert.equal(p3.get('daysSincePrevious'), 1);
+});
+
test('appending posts', function() {
const postStream = buildStream(4567, [1, 3, 4]);
const store = postStream.store;
@@ -96,8 +130,8 @@ test("removePosts", function() {
const store = postStream.store;
const p1 = store.createRecord('post', {id: 1, post_number: 2}),
- p2 = store.createRecord('post', {id: 2, post_number: 3}),
- p3 = store.createRecord('post', {id: 3, post_number: 4});
+ p2 = store.createRecord('post', {id: 2, post_number: 3}),
+ p3 = store.createRecord('post', {id: 3, post_number: 4});
postStream.appendPost(p1);
postStream.appendPost(p2);