FEATURE: Special call-out for new / returning posters. (#7115)

This commit is contained in:
Dan Ungureanu 2019-03-08 10:48:35 +02:00 committed by GitHub
parent 65464969cd
commit 35942f7c7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 174 additions and 1 deletions

View File

@ -134,6 +134,13 @@ export default function transformPost(
postAtts.topicUrl = topic.get("url"); postAtts.topicUrl = topic.get("url");
postAtts.isSaving = post.isSaving; postAtts.isSaving = post.isSaving;
if (post.post_notice_type) {
postAtts.postNoticeType = post.post_notice_type;
if (postAtts.postNoticeType === "returning") {
postAtts.postNoticeTime = new Date(post.post_notice_time);
}
}
const showPMMap = const showPMMap =
topic.archetype === "private_message" && post.post_number === 1; topic.archetype === "private_message" && post.post_number === 1;
if (showPMMap) { if (showPMMap) {

View File

@ -13,6 +13,7 @@ import {
formatUsername formatUsername
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import hbs from "discourse/widgets/hbs-compiler"; import hbs from "discourse/widgets/hbs-compiler";
import { relativeAge } from "discourse/lib/formatter";
function transformWithCallbacks(post) { function transformWithCallbacks(post) {
let transformed = transformBasicPost(post); let transformed = transformBasicPost(post);
@ -427,6 +428,29 @@ createWidget("post-contents", {
} }
}); });
createWidget("post-notice", {
tagName: "div.post-notice",
html(attrs) {
let text, icon;
if (attrs.postNoticeType === "first") {
icon = "hands-helping";
text = I18n.t("post.notice.first", { user: attrs.username });
} else if (attrs.postNoticeType === "returning") {
icon = "far-smile";
text = I18n.t("post.notice.return", {
user: attrs.username,
time: relativeAge(attrs.postNoticeTime, {
format: "tiny",
addAgo: true
})
});
}
return h("p", [iconNode(icon), text]);
}
});
createWidget("post-body", { createWidget("post-body", {
tagName: "div.topic-body.clearfix", tagName: "div.topic-body.clearfix",
@ -505,6 +529,10 @@ createWidget("post-article", {
); );
} }
if (attrs.postNoticeType) {
rows.push(h("div.row", [this.attach("post-notice", attrs)]));
}
rows.push( rows.push(
h("div.row", [ h("div.row", [
this.attach("post-avatar", attrs), this.attach("post-avatar", attrs),

View File

@ -864,3 +864,22 @@ a.mention-group {
margin-bottom: 1em; margin-bottom: 1em;
} }
} }
.post-notice {
background-color: $tertiary-low;
border-top: 1px solid $primary-low;
color: $primary;
padding: 1em;
width: calc(
#{$topic-body-width} + #{$topic-avatar-width} - #{$topic-body-width-padding} +
3px
);
p {
margin: 0;
}
.d-icon {
margin-right: 1em;
}
}

View File

@ -194,6 +194,7 @@ class Post < ActiveRecord::Base
def recover! def recover!
super super
update_flagged_posts_count update_flagged_posts_count
delete_post_notices
recover_public_post_actions recover_public_post_actions
TopicLink.extract_from(self) TopicLink.extract_from(self)
QuotedPost.extract_from(self) QuotedPost.extract_from(self)
@ -381,6 +382,11 @@ class Post < ActiveRecord::Base
PostAction.update_flagged_posts_count PostAction.update_flagged_posts_count
end end
def delete_post_notices
self.custom_fields.delete("post_notice_type")
self.custom_fields.delete("post_notice_time")
end
def recover_public_post_actions def recover_public_post_actions
PostAction.publics PostAction.publics
.with_deleted .with_deleted

View File

@ -70,6 +70,8 @@ class PostSerializer < BasicPostSerializer
:is_auto_generated, :is_auto_generated,
:action_code, :action_code,
:action_code_who, :action_code_who,
:post_notice_type,
:post_notice_time,
:last_wiki_edit, :last_wiki_edit,
:locked, :locked,
:excerpt :excerpt
@ -363,6 +365,22 @@ class PostSerializer < BasicPostSerializer
include_action_code? && action_code_who.present? include_action_code? && action_code_who.present?
end end
def post_notice_type
post_custom_fields["post_notice_type"]
end
def include_post_notice_type?
post_notice_type.present?
end
def post_notice_time
post_custom_fields["post_notice_time"]
end
def include_post_notice_time?
post_notice_time.present?
end
def locked def locked
true true
end end

View File

@ -2149,6 +2149,10 @@ en:
one: "view 1 hidden reply" one: "view 1 hidden reply"
other: "view {{count}} hidden replies" other: "view {{count}} hidden replies"
notice:
first: "This is the first time {{user}} has posted — let's welcome them to our community!"
return: "It's been a while since we've seen {{user}} — their last post was in {{time}}."
unread: "Post is unread" unread: "Post is unread"
has_replies: has_replies:
one: "{{count}} Reply" one: "{{count}} Reply"

View File

@ -1901,6 +1901,8 @@ en:
max_allowed_message_recipients: "Maximum recipients allowed in a message." max_allowed_message_recipients: "Maximum recipients allowed in a message."
watched_words_regular_expressions: "Watched words are regular expressions." watched_words_regular_expressions: "Watched words are regular expressions."
returning_users_days: "How many days should pass before a user is considered to be returning."
default_email_digest_frequency: "How often users receive summary emails by default." default_email_digest_frequency: "How often users receive summary emails by default."
default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences." default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences."
default_email_personal_messages: "Send an email when someone messages the user by default." default_email_personal_messages: "Send an email when someone messages the user by default."

View File

@ -807,6 +807,8 @@ posting:
default: false default: false
client: true client: true
shadowed_by_global: true shadowed_by_global: true
returning_users_days:
default: 60
email: email:
email_time_window_mins: email_time_window_mins:

View File

@ -165,6 +165,7 @@ class PostCreator
transaction do transaction do
build_post_stats build_post_stats
create_topic create_topic
create_post_notice
save_post save_post
extract_links extract_links
track_topic track_topic
@ -508,6 +509,21 @@ class PostCreator
@user.update_attributes(last_posted_at: @post.created_at) @user.update_attributes(last_posted_at: @post.created_at)
end end
def create_post_notice
last_post_time = Post.where(user_id: @user.id)
.order(created_at: :desc)
.limit(1)
.pluck(:created_at)
.first
if !last_post_time
@post.custom_fields["post_notice_type"] = "first"
elsif SiteSetting.returning_users_days > 0 && last_post_time < SiteSetting.returning_users_days.days.ago
@post.custom_fields["post_notice_type"] = "returning"
@post.custom_fields["post_notice_time"] = last_post_time
end
end
def publish def publish
return if @opts[:import_mode] || @post.post_number == 1 return if @opts[:import_mode] || @post.post_number == 1
@post.publish_change_to_clients! :created @post.publish_change_to_clients! :created

View File

@ -118,6 +118,7 @@ module SvgSprite
"globe", "globe",
"globe-americas", "globe-americas",
"hand-point-right", "hand-point-right",
"hands-helping",
"heading", "heading",
"heart", "heart",
"home", "home",

View File

@ -18,7 +18,7 @@ class TopicView
end end
def self.default_post_custom_fields def self.default_post_custom_fields
@default_post_custom_fields ||= ["action_code_who"] @default_post_custom_fields ||= ["action_code_who", "post_notice_type", "post_notice_time"]
end end
def self.post_custom_fields_whitelisters def self.post_custom_fields_whitelisters

View File

@ -1238,4 +1238,32 @@ describe PostCreator do
end end
end end
end end
context "#create_post_notice" do
let(:user) { Fabricate(:user) }
let(:new_user) { Fabricate(:user) }
let(:returning_user) { Fabricate(:user) }
it "generates post notices" do
# new users
post = PostCreator.create(new_user, title: "one of my first topics", raw: "one of my first posts")
expect(post.custom_fields["post_notice_type"]).to eq("first")
post = PostCreator.create(new_user, title: "another one of my first topics", raw: "another one of my first posts")
expect(post.custom_fields["post_notice_type"]).to eq(nil)
# returning users
SiteSetting.returning_users_days = 30
old_post = Fabricate(:post, user: returning_user, created_at: 31.days.ago)
post = PostCreator.create(returning_user, title: "this is a returning topic", raw: "this is a post")
expect(post.custom_fields["post_notice_type"]).to eq("returning")
expect(post.custom_fields["post_notice_time"]).to eq(old_post.created_at.to_s)
end
it "does not generate post notices" do
Fabricate(:post, user: user, created_at: 3.days.ago)
post = PostCreator.create(user, title: "this is another topic", raw: "this is my another post")
expect(post.custom_fields["post_notice_type"]).to eq(nil)
expect(post.custom_fields["post_notice_time"]).to eq(nil)
end
end
end end

View File

@ -134,6 +134,29 @@ describe Post do
end end
end end
context 'a post with notices' do
let(:post) {
post = Fabricate(:post, post_args)
post.custom_fields["post_notice_type"] = "returning"
post.custom_fields["post_notice_time"] = 1.day.ago
post
}
before do
post.trash!
post.reload
end
describe 'recovery' do
it 'deletes notices' do
post.recover!
expect(post.custom_fields).not_to have_key("post_notice_type")
expect(post.custom_fields).not_to have_key("post_notice_time")
end
end
end
end end
describe 'flagging helpers' do describe 'flagging helpers' do

View File

@ -852,3 +852,22 @@ widgetTest("pm map", {
assert.equal(find(".private-message-map .user").length, 1); assert.equal(find(".private-message-map .user").length, 1);
} }
}); });
widgetTest("post notice", {
template: '{{mount-widget widget="post" args=args}}',
beforeEach() {
this.set("args", {
postNoticeType: "returning",
postNoticeTime: new Date("2010-01-01 12:00:00 UTC"),
username: "codinghorror"
});
},
test(assert) {
assert.equal(
find(".post-notice")
.text()
.trim(),
I18n.t("post.notice.return", { user: "codinghorror", time: "Jan '10" })
);
}
});