mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 06:49:14 +08:00
Discourse as a Mailing List improvements
FEATURE: context is not emailed if we previously emailed you the post FEATURE: site setting to enable_watch_new_topics , false by default. When enables users can elect to watch everything by default FIX: Custom email subjects (x quoted you in [title], x replied to [title]) was removed, this broke email grouping. TBD, include info in footer somehow FIX: topic user specs were messy, reduce side effects
This commit is contained in:
parent
293b7e5857
commit
db1d01d1a2
|
@ -180,6 +180,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
'digest_after_days',
|
||||
'new_topic_duration_minutes',
|
||||
'external_links_in_new_tab',
|
||||
'watch_new_topics',
|
||||
'enable_quoting'),
|
||||
type: 'PUT'
|
||||
}).then(function(data) {
|
||||
|
|
|
@ -109,6 +109,15 @@
|
|||
{{combobox valueAttribute="value" content=considerNewTopicOptions value=new_topic_duration_minutes}}
|
||||
</div>
|
||||
|
||||
{{#if Discourse.SiteSettings.enable_watch_new_topics}}
|
||||
<div class="controls">
|
||||
<label>
|
||||
{{view Ember.Checkbox checkedBinding="watch_new_topics"}}
|
||||
{{i18n user.watch_new_topics}}
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="controls">
|
||||
<label>{{view Ember.Checkbox checkedBinding="external_links_in_new_tab"}}
|
||||
{{i18n user.external_links_in_new_tab}}</label>
|
||||
|
|
|
@ -113,12 +113,21 @@ class UserNotifications < ActionMailer::Base
|
|||
notification_type = opts[:notification_type] || Notification.types[@notification.notification_type].to_s
|
||||
|
||||
context = ""
|
||||
tu = TopicUser.get(@post.topic_id, user)
|
||||
|
||||
context_posts = Post.where(topic_id: @post.topic_id)
|
||||
.where("post_number < ?", @post.post_number)
|
||||
.where(user_deleted: false)
|
||||
.order('created_at desc')
|
||||
.limit(SiteSetting.email_posts_context)
|
||||
|
||||
if tu && tu.last_emailed_post_number
|
||||
context_posts = context_posts.where("post_number > ?", tu.last_emailed_post_number)
|
||||
end
|
||||
|
||||
# make .present? cheaper
|
||||
context_posts = context_posts.to_a
|
||||
|
||||
if context_posts.present?
|
||||
context << "---\n*#{I18n.t('user_notifications.previous_discussion')}*\n"
|
||||
context_posts.each do |cp|
|
||||
|
@ -157,6 +166,8 @@ class UserNotifications < ActionMailer::Base
|
|||
email_opts[:from_alias] = username
|
||||
end
|
||||
|
||||
TopicUser.change(user.id, @post.topic_id, last_emailed_post_number: @post.post_number)
|
||||
|
||||
build_email(user.email, email_opts)
|
||||
end
|
||||
|
||||
|
|
|
@ -123,17 +123,16 @@ class PostAlertObserver < ActiveRecord::Observer
|
|||
reply_to_user = post.reply_notification_target
|
||||
notify_users(reply_to_user, :replied, post)
|
||||
|
||||
# find all users watching
|
||||
if post.post_number > 1
|
||||
exclude_user_ids = []
|
||||
exclude_user_ids << post.user_id
|
||||
exclude_user_ids << reply_to_user.id if reply_to_user.present?
|
||||
exclude_user_ids << extract_mentioned_users(post).map(&:id)
|
||||
exclude_user_ids << extract_quoted_users(post).map(&:id)
|
||||
exclude_user_ids.flatten!
|
||||
TopicUser.where(topic_id: post.topic_id, notification_level: TopicUser.notification_levels[:watching]).includes(:user).each do |tu|
|
||||
exclude_user_ids = []
|
||||
exclude_user_ids << post.user_id
|
||||
exclude_user_ids << reply_to_user.id if reply_to_user.present?
|
||||
exclude_user_ids << extract_mentioned_users(post).map(&:id)
|
||||
exclude_user_ids << extract_quoted_users(post).map(&:id)
|
||||
exclude_user_ids.flatten!
|
||||
TopicUser
|
||||
.where(topic_id: post.topic_id, notification_level: TopicUser.notification_levels[:watching])
|
||||
.includes(:user).each do |tu|
|
||||
create_notification(tu.user, Notification.types[:posted], post) unless exclude_user_ids.include?(tu.user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ class TopicUser < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def notification_reasons
|
||||
@notification_reasons ||= Enum.new(:created_topic, :user_changed, :user_interacted, :created_post)
|
||||
@notification_reasons ||= Enum.new(:created_topic, :user_changed, :user_interacted, :created_post, :auto_watch)
|
||||
end
|
||||
|
||||
def auto_track(user_id, topic_id, reason)
|
||||
|
@ -64,6 +64,24 @@ class TopicUser < ActiveRecord::Base
|
|||
TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
|
||||
end
|
||||
|
||||
def auto_watch_new_topic(topic_id)
|
||||
# Can not afford to slow down creation of topics when a pile of users are watching new topics, reverting to SQL for max perf here
|
||||
sql = <<SQL
|
||||
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id)
|
||||
SELECT id, :topic_id, :level, :reason
|
||||
FROM users
|
||||
WHERE watch_new_topics AND
|
||||
NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = :topic_id AND user_id = users.id)
|
||||
SQL
|
||||
|
||||
exec_sql(
|
||||
sql,
|
||||
topic_id: topic_id,
|
||||
level: notification_levels[:watching],
|
||||
reason: notification_reasons[:auto_watch]
|
||||
)
|
||||
end
|
||||
|
||||
# Change attributes for a user (creates a record when none is present). First it tries an update
|
||||
# since there's more likely to be an existing record than not. If the update returns 0 rows affected
|
||||
# it then creates the row instead.
|
||||
|
|
|
@ -24,7 +24,7 @@ class UserSerializer < BasicUserSerializer
|
|||
has_one :invited_by, embed: :object, serializer: BasicUserSerializer
|
||||
|
||||
def self.private_attributes(*attrs)
|
||||
attributes *attrs
|
||||
attributes(*attrs)
|
||||
attrs.each do |attr|
|
||||
define_method "include_#{attr}?" do
|
||||
can_edit
|
||||
|
@ -51,6 +51,7 @@ class UserSerializer < BasicUserSerializer
|
|||
:email_direct,
|
||||
:email_always,
|
||||
:digest_after_days,
|
||||
:watch_new_topics,
|
||||
:auto_track_topics_after_msecs,
|
||||
:new_topic_duration_minutes,
|
||||
:external_links_in_new_tab,
|
||||
|
|
|
@ -30,7 +30,8 @@ class UserUpdater
|
|||
:email_private_messages,
|
||||
:external_links_in_new_tab,
|
||||
:enable_quoting,
|
||||
:dynamic_favicon
|
||||
:dynamic_favicon,
|
||||
:watch_new_topics
|
||||
].each do |attribute|
|
||||
if attributes[attribute].present?
|
||||
user.send("#{attribute.to_s}=", attributes[attribute] == 'true')
|
||||
|
|
|
@ -227,6 +227,7 @@ en:
|
|||
deleted: "(deleted)"
|
||||
suspended_notice: "This user is suspended until {{date}}."
|
||||
suspended_reason: "Reason: "
|
||||
watch_new_topics: "Automatically watch all new topics posted on the forum"
|
||||
|
||||
messages:
|
||||
all: "All"
|
||||
|
@ -693,6 +694,7 @@ en:
|
|||
notifications:
|
||||
title: ''
|
||||
reasons:
|
||||
"3_5": 'You will receive notifications because you started watching this topic automatically.'
|
||||
"3_2": 'You will receive notifications because you are watching this topic.'
|
||||
"3_1": 'You will receive notifications because you created this topic.'
|
||||
"3": 'You will receive notifications because you are watching this topic.'
|
||||
|
|
|
@ -728,6 +728,7 @@ en:
|
|||
pop3s_polling_host: "The host to poll for email via POP3S"
|
||||
pop3s_polling_username: "The username for the POP3S account to poll for email"
|
||||
pop3s_polling_password: "The password for the POP3S account to poll for email"
|
||||
enable_watch_new_topics: "Allow users to watch all new topics (optionally) via a user preference"
|
||||
|
||||
minimum_topics_similar: "How many topics need to exist in the database before similar topics are presented."
|
||||
|
||||
|
@ -1096,7 +1097,7 @@ en:
|
|||
Please visit this link to view the topic: %{base_url}%{url}
|
||||
|
||||
user_replied:
|
||||
subject_template: "[%{site_name}] new reply to your post in '%{topic_title}'"
|
||||
subject_template: "[%{site_name}] %{topic_title}"
|
||||
text_body_template: |
|
||||
%{message}
|
||||
|
||||
|
@ -1106,7 +1107,7 @@ en:
|
|||
%{respond_instructions}
|
||||
|
||||
user_quoted:
|
||||
subject_template: "[%{site_name}] %{username} quoted you in '%{topic_title}'"
|
||||
subject_template: "[%{site_name}] %{topic_title}"
|
||||
text_body_template: |
|
||||
%{message}
|
||||
|
||||
|
@ -1116,7 +1117,7 @@ en:
|
|||
%{respond_instructions}
|
||||
|
||||
user_mentioned:
|
||||
subject_template: "[%{site_name}] %{username} mentioned you in '%{topic_title}'"
|
||||
subject_template: "[%{site_name}] %{topic_title}"
|
||||
text_body_template: |
|
||||
%{message}
|
||||
|
||||
|
@ -1126,7 +1127,7 @@ en:
|
|||
%{respond_instructions}
|
||||
|
||||
user_posted:
|
||||
subject_template: "[%{site_name}] %{subject_prefix}new post in '%{topic_title}'"
|
||||
subject_template: "[%{site_name}] %{topic_title}"
|
||||
text_body_template: |
|
||||
%{message}
|
||||
|
||||
|
|
|
@ -225,6 +225,9 @@ email:
|
|||
pop3s_polling_port: 995
|
||||
pop3s_polling_username: ''
|
||||
pop3s_polling_password: ''
|
||||
enable_watch_new_topics:
|
||||
default: false
|
||||
client: true
|
||||
|
||||
files:
|
||||
max_image_size_kb:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddWatchNewTopicsToUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :watch_new_topics, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddLastEmailedPostNumberToTopicUser < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :topic_users, :last_emailed_post_number, :integer
|
||||
end
|
||||
end
|
|
@ -32,6 +32,7 @@ class TopicCreator
|
|||
unless @opts[:auto_track] == false
|
||||
@topic.notifier.watch_topic!(@topic.user_id)
|
||||
end
|
||||
TopicUser.auto_watch_new_topic(@topic.id)
|
||||
end
|
||||
|
||||
def setup
|
||||
|
|
|
@ -64,6 +64,9 @@ describe UserNotifications do
|
|||
# 1 unsubscribe
|
||||
mail.html_part.to_s.scan(/To unsubscribe/).count.should == 1
|
||||
|
||||
# side effect, topic user is updated with post number
|
||||
tu = TopicUser.get(post.topic_id, response.user)
|
||||
tu.last_emailed_post_number.should == response.post_number
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
require 'discourse'
|
||||
require 'spec_helper'
|
||||
|
||||
describe TopicLinkClick do
|
||||
|
|
|
@ -5,17 +5,12 @@ describe TopicUser do
|
|||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
|
||||
let!(:yesterday) { DateTime.now.yesterday }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
DateTime.expects(:now).at_least_once.returns(yesterday)
|
||||
end
|
||||
|
||||
let!(:user) { Fabricate(:coding_horror) }
|
||||
let!(:topic) {
|
||||
user = Fabricate(:user)
|
||||
guardian = Guardian.new(user)
|
||||
TopicCreator.create(user, guardian, title: "this is my topic title")
|
||||
let(:topic) {
|
||||
u = Fabricate(:user)
|
||||
guardian = Guardian.new(u)
|
||||
TopicCreator.create(u, guardian, title: "this is my topic title")
|
||||
}
|
||||
let(:topic_user) { TopicUser.get(topic,user) }
|
||||
let(:topic_creator_user) { TopicUser.get(topic, topic.user) }
|
||||
|
@ -23,6 +18,7 @@ describe TopicUser do
|
|||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
let(:new_user) { Fabricate(:user, auto_track_topics_after_msecs: 1000) }
|
||||
let(:topic_new_user) { TopicUser.get(topic, new_user)}
|
||||
let(:yesterday) { DateTime.now.yesterday }
|
||||
|
||||
|
||||
describe "unpinned" do
|
||||
|
@ -96,19 +92,22 @@ describe TopicUser do
|
|||
end
|
||||
|
||||
it 'set upon initial visit' do
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
freeze_time yesterday do
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates upon repeat visit' do
|
||||
today = yesterday.tomorrow
|
||||
DateTime.expects(:now).returns(today)
|
||||
|
||||
TopicUser.track_visit!(topic,user)
|
||||
# reload is a no go
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == today.to_i
|
||||
freeze_time today do
|
||||
TopicUser.track_visit!(topic,user)
|
||||
# reload is a no go
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == today.to_i
|
||||
end
|
||||
end
|
||||
|
||||
it 'triggers the observer callbacks when updating' do
|
||||
|
@ -128,18 +127,22 @@ describe TopicUser do
|
|||
let(:topic_user) { TopicUser.get(topic,user) }
|
||||
|
||||
it 'should create a new record for a visit' do
|
||||
topic_user.last_read_post_number.should == 1
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
freeze_time yesterday do
|
||||
topic_user.last_read_post_number.should == 1
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
end
|
||||
|
||||
it 'should update the record for repeat visit' do
|
||||
Fabricate(:post, topic: topic, user: user)
|
||||
TopicUser.update_last_read(user, topic.id, 2, 0)
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.last_read_post_number.should == 2
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
freeze_time yesterday do
|
||||
Fabricate(:post, topic: topic, user: user)
|
||||
TopicUser.update_last_read(user, topic.id, 2, 0)
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.last_read_post_number.should == 2
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -181,12 +184,17 @@ describe TopicUser do
|
|||
describe 'change a flag' do
|
||||
|
||||
it 'creates a forum topic user record' do
|
||||
user; topic
|
||||
|
||||
lambda {
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
}.should change(TopicUser, :count).by(1)
|
||||
end
|
||||
|
||||
it "only inserts a row once, even on repeated calls" do
|
||||
|
||||
topic; user
|
||||
|
||||
lambda {
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
TopicUser.change(user, topic.id, starred: false)
|
||||
|
@ -248,4 +256,22 @@ describe TopicUser do
|
|||
tu.seen_post_count.should == 2
|
||||
end
|
||||
|
||||
describe "auto_watch_new_topic" do
|
||||
|
||||
it "auto watches topics when called" do
|
||||
user1 = Fabricate(:user)
|
||||
user2 = Fabricate(:user, watch_new_topics: true)
|
||||
user3 = Fabricate(:user, watch_new_topics: true)
|
||||
TopicUser.auto_watch_new_topic(topic.id)
|
||||
|
||||
TopicUser.get(topic, user1).should == nil
|
||||
|
||||
tu = TopicUser.get(topic, user2)
|
||||
tu.notification_level.should == TopicUser.notification_levels[:watching]
|
||||
tu.notifications_reason_id.should == TopicUser.notification_reasons[:auto_watch]
|
||||
|
||||
TopicUser.get(topic, user3).notification_level.should == TopicUser.notification_levels[:watching]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user