FEATURE: add support for defer topic functionality

This feature allows end users to "defer" topics by marking them unread

The functionality is default disabled. This also introduces the new site
setting default_other_enable_defer: to enable this by default on new user
accounts.
This commit is contained in:
Sam Saffron 2019-05-31 15:43:17 +10:00
parent 5c526e5abb
commit 3b8819f0ab
16 changed files with 83 additions and 5 deletions

View File

@ -32,6 +32,8 @@ export default Ember.Component.extend({
canInviteTo: Ember.computed.alias("topic.details.can_invite_to"), canInviteTo: Ember.computed.alias("topic.details.can_invite_to"),
canDefer: Ember.computed.alias("currentUser.enable_defer"),
inviteDisabled: Ember.computed.or( inviteDisabled: Ember.computed.or(
"topic.archived", "topic.archived",
"topic.closed", "topic.closed",

View File

@ -31,6 +31,7 @@ export default Ember.Controller.extend(PreferencesTabController, {
"external_links_in_new_tab", "external_links_in_new_tab",
"dynamic_favicon", "dynamic_favicon",
"enable_quoting", "enable_quoting",
"enable_defer",
"automatically_unpin_topics", "automatically_unpin_topics",
"allow_private_messages", "allow_private_messages",
"homepage_id", "homepage_id",

View File

@ -405,6 +405,27 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
} }
}, },
deferTopic() {
const screenTrack = Discourse.__container__.lookup("screen-track:main");
const currentUser = this.currentUser;
const topic = this.model;
screenTrack.reset();
screenTrack.stop();
const goToPath = topic.get("isPrivateMessage")
? currentUser.pmPath(topic)
: "/";
ajax("/t/" + topic.get("id") + "/timings.json?last=1", { type: "DELETE" })
.then(() => {
const highestSeenByTopic = Discourse.Session.currentProp(
"highestSeenByTopic"
);
highestSeenByTopic[topic.get("id")] = null;
DiscourseURL.routeTo(goToPath);
})
.catch(popupAjaxError);
},
editFirstPost() { editFirstPost() {
const postStream = this.get("model.postStream"); const postStream = this.get("model.postStream");
let firstPost = postStream.get("posts.firstObject"); let firstPost = postStream.get("posts.firstObject");

View File

@ -148,5 +148,20 @@ export default {
return this.showEditOnFooter; return this.showEditOnFooter;
} }
}); });
registerTopicFooterButton({
id: "defer",
icon: "circle",
priority: 300,
label: "topic.defer.title",
title: "topic.defer.help",
action: "deferTopic",
displayed() {
return this.canDefer;
},
dropdown() {
return this.site.mobileView;
}
});
} }
}; };

View File

@ -271,6 +271,7 @@ const User = RestModel.extend({
"email_previous_replies", "email_previous_replies",
"dynamic_favicon", "dynamic_favicon",
"enable_quoting", "enable_quoting",
"enable_defer",
"automatically_unpin_topics", "automatically_unpin_topics",
"digest_after_minutes", "digest_after_minutes",
"new_topic_duration_minutes", "new_topic_duration_minutes",
@ -338,6 +339,7 @@ const User = RestModel.extend({
const userProps = Ember.getProperties( const userProps = Ember.getProperties(
this.user_option, this.user_option,
"enable_quoting", "enable_quoting",
"enable_defer",
"external_links_in_new_tab", "external_links_in_new_tab",
"dynamic_favicon" "dynamic_favicon"
); );

View File

@ -49,6 +49,7 @@
{{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}} {{preference-checkbox labelKey="user.external_links_in_new_tab" checked=model.user_option.external_links_in_new_tab}}
{{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}} {{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}}
{{preference-checkbox labelKey="user.enable_defer" checked=model.user_option.enable_defer}}
{{#if siteSettings.automatically_unpin_topics}} {{#if siteSettings.automatically_unpin_topics}}
{{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}} {{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}}
{{/if}} {{/if}}

View File

@ -301,6 +301,7 @@
showFlagTopic=(route-action "showFlagTopic") showFlagTopic=(route-action "showFlagTopic")
toggleArchiveMessage=(action "toggleArchiveMessage") toggleArchiveMessage=(action "toggleArchiveMessage")
editFirstPost=(action "editFirstPost") editFirstPost=(action "editFirstPost")
deferTopic=(action "deferTopic")
replyToPost=(action "replyToPost")}} replyToPost=(action "replyToPost")}}
{{else}} {{else}}
<div id="topic-footer-buttons"> <div id="topic-footer-buttons">

View File

@ -53,6 +53,7 @@ class UserOption < ActiveRecord::Base
self.email_in_reply_to = SiteSetting.default_email_in_reply_to self.email_in_reply_to = SiteSetting.default_email_in_reply_to
self.enable_quoting = SiteSetting.default_other_enable_quoting self.enable_quoting = SiteSetting.default_other_enable_quoting
self.enable_defer = SiteSetting.default_other_enable_defer
self.external_links_in_new_tab = SiteSetting.default_other_external_links_in_new_tab self.external_links_in_new_tab = SiteSetting.default_other_external_links_in_new_tab
self.dynamic_favicon = SiteSetting.default_other_dynamic_favicon self.dynamic_favicon = SiteSetting.default_other_dynamic_favicon
@ -222,6 +223,7 @@ end
# email_level :integer default(1), not null # email_level :integer default(1), not null
# email_messages_level :integer default(0), not null # email_messages_level :integer default(0), not null
# title_count_mode_key :integer default(0), not null # title_count_mode_key :integer default(0), not null
# enable_defer :boolean default(FALSE), not null
# #
# Indexes # Indexes
# #

View File

@ -16,6 +16,7 @@ class CurrentUserSerializer < BasicUserSerializer
:reply_count, :reply_count,
:topic_count, :topic_count,
:enable_quoting, :enable_quoting,
:enable_defer,
:external_links_in_new_tab, :external_links_in_new_tab,
:dynamic_favicon, :dynamic_favicon,
:trust_level, :trust_level,
@ -79,6 +80,10 @@ class CurrentUserSerializer < BasicUserSerializer
object.user_option.enable_quoting object.user_option.enable_quoting
end end
def enable_defer
object.user_option.enable_defer
end
def external_links_in_new_tab def external_links_in_new_tab
object.user_option.external_links_in_new_tab object.user_option.external_links_in_new_tab
end end

View File

@ -10,6 +10,7 @@ class UserOptionSerializer < ApplicationSerializer
:external_links_in_new_tab, :external_links_in_new_tab,
:dynamic_favicon, :dynamic_favicon,
:enable_quoting, :enable_quoting,
:enable_defer,
:digest_after_minutes, :digest_after_minutes,
:automatically_unpin_topics, :automatically_unpin_topics,
:auto_track_topics_after_msecs, :auto_track_topics_after_msecs,

View File

@ -24,6 +24,7 @@ class UserUpdater
:email_messages_level, :email_messages_level,
:external_links_in_new_tab, :external_links_in_new_tab,
:enable_quoting, :enable_quoting,
:enable_defer,
:dynamic_favicon, :dynamic_favicon,
:automatically_unpin_topics, :automatically_unpin_topics,
:digest_after_minutes, :digest_after_minutes,

View File

@ -803,6 +803,7 @@ en:
allow_private_messages: "Allow other users to send me personal messages" allow_private_messages: "Allow other users to send me personal messages"
external_links_in_new_tab: "Open all external links in a new tab" external_links_in_new_tab: "Open all external links in a new tab"
enable_quoting: "Enable quote reply for highlighted text" enable_quoting: "Enable quote reply for highlighted text"
enable_defer: "Enable defer to mark topics unread"
change: "change" change: "change"
moderator: "{{user}} is a moderator" moderator: "{{user}} is a moderator"
admin: "{{user}} is an admin" admin: "{{user}} is an admin"
@ -1880,6 +1881,9 @@ en:
edit_message: edit_message:
help: "Edit first post of the message" help: "Edit first post of the message"
title: "Edit Message" title: "Edit Message"
defer:
help: "Mark as unread"
title: "Defer"
list: "Topics" list: "Topics"
new: "new topic" new: "new topic"
unread: "unread" unread: "unread"

View File

@ -1978,6 +1978,7 @@ en:
default_other_notification_level_when_replying: "Global default notification level when the user replies to a topic." default_other_notification_level_when_replying: "Global default notification level when the user replies to a topic."
default_other_external_links_in_new_tab: "Open external links in a new tab by default." default_other_external_links_in_new_tab: "Open external links in a new tab by default."
default_other_enable_quoting: "Enable quote reply for highlighted text by default." default_other_enable_quoting: "Enable quote reply for highlighted text by default."
default_other_enable_defer: "Enable defer topic functionality by default."
default_other_dynamic_favicon: "Show new/updated topic count on browser icon by default." default_other_dynamic_favicon: "Show new/updated topic count on browser icon by default."
default_other_like_notification_frequency: "Notify users on likes by default" default_other_like_notification_frequency: "Notify users on likes by default"

View File

@ -1939,6 +1939,7 @@ user_preferences:
default: 2 default: 2
default_other_external_links_in_new_tab: false default_other_external_links_in_new_tab: false
default_other_enable_quoting: true default_other_enable_quoting: true
default_other_enable_defer: false
default_other_dynamic_favicon: false default_other_dynamic_favicon: false
default_other_like_notification_frequency: default_other_like_notification_frequency:
enum: "LikeNotificationFrequencySiteSetting" enum: "LikeNotificationFrequencySiteSetting"

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddEnableDeferToUserOptions < ActiveRecord::Migration[5.2]
def change
add_column :user_options, :enable_defer, :boolean, default: false, null: false
end
end

View File

@ -18,7 +18,7 @@ describe UserOption do
end end
end end
describe "should_be_redirected_to_top" do describe "defaults" do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
it "should be redirected to top when there is a reason to" do it "should be redirected to top when there is a reason to" do
@ -30,16 +30,29 @@ describe UserOption do
user.user_option.expects(:redirected_to_top).returns(nil) user.user_option.expects(:redirected_to_top).returns(nil)
expect(user.user_option.should_be_redirected_to_top).to eq(false) expect(user.user_option.should_be_redirected_to_top).to eq(false)
end end
end
describe "defaults" do
fab!(:user) { Fabricate(:user) }
it "should not hide the profile and presence by default" do it "should not hide the profile and presence by default" do
expect(user.user_option.hide_profile_and_presence).to eq(false) expect(user.user_option.hide_profile_and_presence).to eq(false)
end end
end end
describe "site settings" do
it "should apply defaults from site settings" do
SiteSetting.default_other_enable_quoting = false
SiteSetting.default_other_enable_defer = true
SiteSetting.default_other_external_links_in_new_tab = true
SiteSetting.default_other_dynamic_favicon = true
user = Fabricate(:user)
expect(user.user_option.enable_quoting).to eq(false)
expect(user.user_option.enable_defer).to eq(true)
expect(user.user_option.external_links_in_new_tab).to eq(true)
expect(user.user_option.dynamic_favicon).to eq(true)
end
end
describe "#mailing_list_mode" do describe "#mailing_list_mode" do
fab!(:forum_user) { Fabricate(:user) } fab!(:forum_user) { Fabricate(:user) }
fab!(:mailing_list_user) { Fabricate(:user) } fab!(:mailing_list_user) { Fabricate(:user) }