FEATURE: Topic slow mode. (#10904)
Adds a new slow mode for topics that are heating up. Users will have to wait for a period of time before being able to post again. We store this interval inside the topics table and track the last time a user posted using the last_posted_at datetime in the TopicUser relation.
This commit is contained in:
parent
4669e60ce5
commit
21c53ed249
|
@ -4,6 +4,7 @@ import EmberObject from "@ember/object";
|
|||
import { scheduleOnce } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
import LinkLookup from "discourse/lib/link-lookup";
|
||||
import { durationTextFromSeconds } from "discourse/helpers/slow-mode";
|
||||
|
||||
let _messagesCache = {};
|
||||
|
||||
|
@ -116,6 +117,21 @@ export default Component.extend({
|
|||
}
|
||||
}
|
||||
|
||||
const topic = composer.topic;
|
||||
if (topic && topic.slow_mode_seconds) {
|
||||
const msg = composer.store.createRecord("composer-message", {
|
||||
id: "slow-mode-enabled",
|
||||
extraClass: "custom-body",
|
||||
templateName: "custom-body",
|
||||
title: I18n.t("composer.slow_mode.title"),
|
||||
body: I18n.t("composer.slow_mode.body", {
|
||||
duration: durationTextFromSeconds(topic.slow_mode_seconds),
|
||||
}),
|
||||
});
|
||||
|
||||
this.send("popup", msg);
|
||||
}
|
||||
|
||||
this.queuedForTyping.forEach((msg) => this.send("popup", msg));
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { durationTextFromSeconds } from "discourse/helpers/slow-mode";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Topic from "discourse/models/topic";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Component.extend({
|
||||
@discourseComputed("topic.slow_mode_seconds")
|
||||
durationText(seconds) {
|
||||
return durationTextFromSeconds(seconds);
|
||||
},
|
||||
|
||||
@discourseComputed("topic.slow_mode_seconds", "topic.closed")
|
||||
showSlowModeNotice(seconds, closed) {
|
||||
return seconds > 0 && !closed;
|
||||
},
|
||||
|
||||
@action
|
||||
disableSlowMode() {
|
||||
Topic.setSlowMode(this.topic.id, 0)
|
||||
.catch(popupAjaxError)
|
||||
.then(() => this.set("topic.slow_mode_seconds", 0));
|
||||
},
|
||||
});
|
|
@ -0,0 +1,112 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
import Topic from "discourse/models/topic";
|
||||
import { fromSeconds, toSeconds } from "discourse/helpers/slow-mode";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
selectedSlowMode: null,
|
||||
hours: null,
|
||||
minutes: null,
|
||||
seconds: null,
|
||||
saveDisabled: false,
|
||||
showCustomSelect: equal("selectedSlowMode", "custom"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.set("slowModes", [
|
||||
{
|
||||
id: "900",
|
||||
name: I18n.t("topic.slow_mode_update.durations.15_minutes"),
|
||||
},
|
||||
{
|
||||
id: "3600",
|
||||
name: I18n.t("topic.slow_mode_update.durations.1_hour"),
|
||||
},
|
||||
{
|
||||
id: "14400",
|
||||
name: I18n.t("topic.slow_mode_update.durations.4_hours"),
|
||||
},
|
||||
{
|
||||
id: "86400",
|
||||
name: I18n.t("topic.slow_mode_update.durations.1_day"),
|
||||
},
|
||||
{
|
||||
id: "604800",
|
||||
name: I18n.t("topic.slow_mode_update.durations.1_week"),
|
||||
},
|
||||
{
|
||||
id: "custom",
|
||||
name: I18n.t("topic.slow_mode_update.durations.custom"),
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const currentDuration = parseInt(this.model.slow_mode_seconds, 10);
|
||||
|
||||
if (currentDuration) {
|
||||
const selectedDuration = this.slowModes.find((mode) => {
|
||||
return mode.id === currentDuration.toString();
|
||||
});
|
||||
|
||||
if (selectedDuration) {
|
||||
this.set("selectedSlowMode", currentDuration.toString());
|
||||
} else {
|
||||
this.set("selectedSlowMode", "custom");
|
||||
}
|
||||
|
||||
this._setFromSeconds(currentDuration);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("hours", "minutes", "seconds")
|
||||
submitDisabled(hours, minutes, seconds) {
|
||||
return this.saveDisabled || !(hours || minutes || seconds);
|
||||
},
|
||||
|
||||
_setFromSeconds(seconds) {
|
||||
this.setProperties(fromSeconds(seconds));
|
||||
},
|
||||
|
||||
@action
|
||||
setSlowModeDuration(duration) {
|
||||
if (duration !== "custom") {
|
||||
let seconds = parseInt(duration, 10);
|
||||
|
||||
this._setFromSeconds(seconds);
|
||||
}
|
||||
|
||||
this.set("selectedSlowMode", duration);
|
||||
},
|
||||
|
||||
@action
|
||||
enableSlowMode() {
|
||||
this.set("saveDisabled", true);
|
||||
const seconds = toSeconds(this.hours, this.minutes, this.seconds);
|
||||
Topic.setSlowMode(this.model.id, seconds)
|
||||
.catch(popupAjaxError)
|
||||
.then(() => {
|
||||
this.set("model.slow_mode_seconds", seconds);
|
||||
this.send("closeModal");
|
||||
})
|
||||
.finally(() => this.set("saveDisabled", false));
|
||||
},
|
||||
|
||||
@action
|
||||
disableSlowMode() {
|
||||
this.set("saveDisabled", true);
|
||||
Topic.setSlowMode(this.model.id, 0)
|
||||
.catch(popupAjaxError)
|
||||
.then(() => {
|
||||
this.set("model.slow_mode_seconds", 0);
|
||||
this.send("closeModal");
|
||||
})
|
||||
.finally(() => this.set("saveDisabled", false));
|
||||
},
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
export function fromSeconds(seconds) {
|
||||
let initialSeconds = seconds;
|
||||
|
||||
let hours = initialSeconds / 3600;
|
||||
if (hours >= 1) {
|
||||
initialSeconds = initialSeconds - 3600 * hours;
|
||||
} else {
|
||||
hours = 0;
|
||||
}
|
||||
|
||||
let minutes = initialSeconds / 60;
|
||||
if (minutes >= 1) {
|
||||
initialSeconds = initialSeconds - 60 * minutes;
|
||||
} else {
|
||||
minutes = 0;
|
||||
}
|
||||
|
||||
return { hours, minutes, seconds: initialSeconds };
|
||||
}
|
||||
|
||||
export function toSeconds(hours, minutes, seconds) {
|
||||
const hoursAsSeconds = parseInt(hours, 10) * 60 * 60;
|
||||
const minutesAsSeconds = parseInt(minutes, 10) * 60;
|
||||
|
||||
return parseInt(seconds, 10) + hoursAsSeconds + minutesAsSeconds;
|
||||
}
|
||||
|
||||
export function durationTextFromSeconds(seconds) {
|
||||
return moment.duration(seconds, "seconds").humanize();
|
||||
}
|
|
@ -845,6 +845,11 @@ Topic.reopenClass({
|
|||
idForSlug(slug) {
|
||||
return ajax(`/t/id_for/${slug}`);
|
||||
},
|
||||
|
||||
setSlowMode(topicId, seconds) {
|
||||
const data = { seconds };
|
||||
return ajax(`/t/${topicId}/slow_mode`, { type: "PUT", data });
|
||||
},
|
||||
});
|
||||
|
||||
function moveResult(result) {
|
||||
|
|
|
@ -118,6 +118,12 @@ const TopicRoute = DiscourseRoute.extend({
|
|||
this.controllerFor("modal").set("modalClass", "edit-topic-timer-modal");
|
||||
},
|
||||
|
||||
showTopicSlowModeUpdate() {
|
||||
const model = this.modelFor("topic");
|
||||
|
||||
showModal("edit-slow-mode", { model });
|
||||
},
|
||||
|
||||
showChangeTimestamp() {
|
||||
showModal("change-timestamp", {
|
||||
model: this.modelFor("topic"),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{{#if showSlowModeNotice}}
|
||||
<div class="topic-status-info">
|
||||
<h3 class="slow-mode-heading">
|
||||
<span>
|
||||
{{d-icon "hourglass-end"}}
|
||||
{{i18n "topic.slow_mode_notice.duration" duration=durationText}}
|
||||
</span>
|
||||
|
||||
{{d-button class="slow-mode-remove"
|
||||
action=(action "disableSlowMode")
|
||||
icon="trash-alt"}}
|
||||
</h3>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -3,6 +3,7 @@
|
|||
topic=topic
|
||||
openUpwards="true"
|
||||
toggleMultiSelect=toggleMultiSelect
|
||||
showTopicSlowModeUpdate=showTopicSlowModeUpdate
|
||||
deleteTopic=deleteTopic
|
||||
recoverTopic=recoverTopic
|
||||
toggleFeaturedOnProfile=toggleFeaturedOnProfile
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{{#d-modal-body title="topic.slow_mode_update.title" autoFocus=false}}
|
||||
<div class="control-group">
|
||||
<label class="slow-mode-label">{{i18n "topic.slow_mode_update.description"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="slow-mode-label">{{i18n "topic.slow_mode_update.select"}}</label>
|
||||
{{combo-box
|
||||
class="slow-mode-type"
|
||||
content=slowModes
|
||||
value=selectedSlowMode
|
||||
onChange=(action "setSlowModeDuration")
|
||||
}}
|
||||
</div>
|
||||
|
||||
{{#if showCustomSelect}}
|
||||
<div class="control-group">
|
||||
{{d-icon "hourglass-end"}}
|
||||
{{input value=hours type="number" class="input-small" placeholder=(i18n "topic.slow_mode_update.hours")}}
|
||||
{{input value=minutes type="number" class="input-small" placeholder=(i18n "topic.slow_mode_update.minutes")}}
|
||||
{{input value=seconds type="number" class="input-small" placeholder=(i18n "topic.slow_mode_update.seconds")}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if model.slow_mode_seconds}}
|
||||
<div class="alert alert-info">
|
||||
<b>
|
||||
{{i18n "topic.slow_mode_update.current" hours=hours minutes=minutes seconds=seconds}}
|
||||
</b>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary"
|
||||
disabled=submitDisabled
|
||||
label="topic.slow_mode_update.save"
|
||||
action=(action "enableSlowMode")}}
|
||||
|
||||
{{conditional-loading-spinner size="small" condition=loading}}
|
||||
|
||||
{{#if model.slow_mode_seconds}}
|
||||
{{d-button class="btn-danger"
|
||||
action=(action "disableSlowMode")
|
||||
disabled=submitDisabled
|
||||
label="topic.slow_mode_update.remove"}}
|
||||
{{/if}}
|
||||
</div>
|
|
@ -145,6 +145,7 @@
|
|||
jumpToIndex=(action "jumpToIndex")
|
||||
replyToPost=(action "replyToPost")
|
||||
toggleMultiSelect=(action "toggleMultiSelect")
|
||||
showTopicSlowModeUpdate=(route-action "showTopicSlowModeUpdate")
|
||||
deleteTopic=(action "deleteTopic")
|
||||
recoverTopic=(action "recoverTopic")
|
||||
toggleClosed=(action "toggleClosed")
|
||||
|
@ -168,6 +169,7 @@
|
|||
openUpwards="true"
|
||||
rightSide="true"
|
||||
toggleMultiSelect=(action "toggleMultiSelect")
|
||||
showTopicSlowModeUpdate=(route-action "showTopicSlowModeUpdate")
|
||||
deleteTopic=(action "deleteTopic")
|
||||
recoverTopic=(action "recoverTopic")
|
||||
toggleClosed=(action "toggleClosed")
|
||||
|
@ -286,6 +288,8 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{slow-mode-info topic=model user=currentUser}}
|
||||
|
||||
{{topic-timer-info
|
||||
topicClosed=model.closed
|
||||
statusType=model.topic_timer.status_type
|
||||
|
@ -305,6 +309,7 @@
|
|||
{{topic-footer-buttons
|
||||
topic=model
|
||||
toggleMultiSelect=(action "toggleMultiSelect")
|
||||
showTopicSlowModeUpdate=(route-action "showTopicSlowModeUpdate")
|
||||
deleteTopic=(action "deleteTopic")
|
||||
recoverTopic=(action "recoverTopic")
|
||||
toggleClosed=(action "toggleClosed")
|
||||
|
|
|
@ -164,6 +164,14 @@ export default createWidget("topic-admin-menu", {
|
|||
});
|
||||
}
|
||||
|
||||
this.addActionButton({
|
||||
className: "topic-admin-slow-mode",
|
||||
buttonClass: "popup-menu-btn",
|
||||
action: "showTopicSlowModeUpdate",
|
||||
icon: "hourglass-end",
|
||||
label: "actions.slow_mode",
|
||||
});
|
||||
|
||||
if (topic.get("deleted") && details.get("can_recover")) {
|
||||
this.addActionButton({
|
||||
className: "topic-admin-recover",
|
||||
|
|
|
@ -811,3 +811,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal.edit-slow-mode-modal {
|
||||
.slow-mode-label {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.alert.alert-info {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-small {
|
||||
width: 15%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,12 +63,14 @@
|
|||
border-top: 1px solid var(--primary-low);
|
||||
padding: 10px 0;
|
||||
max-width: 758px;
|
||||
.topic-timer-heading {
|
||||
.topic-timer-heading,
|
||||
.slow-mode-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0px;
|
||||
}
|
||||
.topic-timer-remove {
|
||||
.topic-timer-remove,
|
||||
.slow-mode-remove {
|
||||
font-size: $font-down-2;
|
||||
background: transparent;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -28,7 +28,8 @@ class TopicsController < ApplicationController
|
|||
:convert_topic,
|
||||
:bookmark,
|
||||
:publish,
|
||||
:reset_bump_date
|
||||
:reset_bump_date,
|
||||
:set_slow_mode
|
||||
]
|
||||
|
||||
before_action :consider_user_for_promotion, only: :show
|
||||
|
@ -932,6 +933,15 @@ class TopicsController < ApplicationController
|
|||
render body: nil
|
||||
end
|
||||
|
||||
def set_slow_mode
|
||||
topic = Topic.find(params[:topic_id])
|
||||
|
||||
guardian.ensure_can_moderate!(topic)
|
||||
topic.update!(slow_mode_seconds: params[:seconds])
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def topic_params
|
||||
|
|
|
@ -1770,6 +1770,7 @@ end
|
|||
# archetype :string default("regular"), not null
|
||||
# featured_user4_id :integer
|
||||
# notify_moderators_count :integer default(0), not null
|
||||
# slow_mode_seconds :integer default(0), not null
|
||||
# spam_count :integer default(0), not null
|
||||
# pinned_at :datetime
|
||||
# score :float
|
||||
|
|
|
@ -495,6 +495,7 @@ end
|
|||
# posted :boolean default(FALSE), not null
|
||||
# last_read_post_number :integer
|
||||
# highest_seen_post_number :integer
|
||||
# last_posted_at :datetime
|
||||
# last_visited_at :datetime
|
||||
# first_visited_at :datetime
|
||||
# notification_level :integer default(1), not null
|
||||
|
|
|
@ -40,7 +40,8 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
:pinned_globally,
|
||||
:pinned_at,
|
||||
:pinned_until,
|
||||
:image_url
|
||||
:image_url,
|
||||
:slow_mode_seconds
|
||||
)
|
||||
|
||||
attributes(
|
||||
|
@ -72,7 +73,8 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
:queued_posts_count,
|
||||
:show_read_indicator,
|
||||
:requested_group_name,
|
||||
:thumbnails
|
||||
:thumbnails,
|
||||
:user_last_posted_at
|
||||
)
|
||||
|
||||
has_one :details, serializer: TopicViewDetailsSerializer, root: false, embed: :objects
|
||||
|
@ -280,4 +282,12 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
extra_sizes = ThemeModifierHelper.new(request: scope.request).topic_thumbnail_sizes
|
||||
object.topic.thumbnail_info(enqueue_if_missing: true, extra_sizes: extra_sizes)
|
||||
end
|
||||
|
||||
def user_last_posted_at
|
||||
object.topic_user.last_posted_at
|
||||
end
|
||||
|
||||
def include_user_last_posted_at?
|
||||
object.topic.slow_mode_seconds.to_i > 0
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@ class WebHookTopicViewSerializer < TopicViewSerializer
|
|||
topic_timer
|
||||
details
|
||||
image_url
|
||||
slow_mode_seconds
|
||||
}.each do |attr|
|
||||
define_method("include_#{attr}?") do
|
||||
false
|
||||
|
|
|
@ -1922,6 +1922,9 @@ en:
|
|||
yourself_confirm:
|
||||
title: "Did you forget to add recipients?"
|
||||
body: "Right now this message is only being sent to yourself!"
|
||||
slow_mode:
|
||||
title: "This topic is in slow mode."
|
||||
body: "After submitting a post, you'll need to wait %{duration} before being able to post again."
|
||||
|
||||
admin_options_title: "Optional staff settings for this topic"
|
||||
|
||||
|
@ -2313,6 +2316,25 @@ en:
|
|||
jump_reply_down: jump to later reply
|
||||
deleted: "The topic has been deleted"
|
||||
|
||||
slow_mode_update:
|
||||
title: "Slow Mode"
|
||||
select: "Duration:"
|
||||
description: "Users will have to wait to be able to post again."
|
||||
current: "Current duration is %{hours} hours, %{minutes} minutes, and %{seconds} seconds."
|
||||
save: "Save"
|
||||
remove: "Disable"
|
||||
hours: "Hours"
|
||||
minutes: "Minutes"
|
||||
seconds: "Seconds"
|
||||
durations:
|
||||
15_minutes: "15 Minutes"
|
||||
1_hour: "1 Hour"
|
||||
4_hours: "4 Hours"
|
||||
1_day: "1 Day"
|
||||
1_week: "1 Week"
|
||||
custom: "Pick Duration"
|
||||
slow_mode_notice:
|
||||
duration: "You need to wait %{duration} between posts in this topic"
|
||||
topic_status_update:
|
||||
title: "Topic Timer"
|
||||
save: "Set Timer"
|
||||
|
@ -2447,6 +2469,7 @@ en:
|
|||
open: "Open Topic"
|
||||
close: "Close Topic"
|
||||
multi_select: "Select Posts…"
|
||||
slow_mode: "Set Slow Mode"
|
||||
timed_update: "Set Topic Timer..."
|
||||
pin: "Pin Topic…"
|
||||
unpin: "Un-Pin Topic…"
|
||||
|
|
|
@ -340,6 +340,7 @@ en:
|
|||
removed_direct_reply_full_quotes: "Automatically removed quote of whole previous post."
|
||||
secure_upload_not_allowed_in_public_topic: "Sorry, the following secure upload(s) cannot be used in a public topic: %{upload_filenames}."
|
||||
create_pm_on_existing_topic: "Sorry, you can't create a PM on an existing topic."
|
||||
slow_mode_enabled: "You recently posted on this topic, which is in slow mode. Please wait so other users can have their chance to participate."
|
||||
|
||||
just_posted_that: "is too similar to what you recently posted"
|
||||
invalid_characters: "contains invalid characters"
|
||||
|
|
|
@ -804,6 +804,7 @@ Discourse::Application.routes.draw do
|
|||
put "t/:topic_id/bookmark" => "topics#bookmark", constraints: { topic_id: /\d+/ }
|
||||
put "t/:topic_id/remove_bookmarks" => "topics#remove_bookmarks", constraints: { topic_id: /\d+/ }
|
||||
put "t/:topic_id/tags" => "topics#update_tags", constraints: { topic_id: /\d+/ }
|
||||
put "t/:topic_id/slow_mode" => "topics#set_slow_mode", constraints: { topic_id: /\d+/ }
|
||||
|
||||
post "t/:topic_id/notifications" => "topics#set_notifications" , constraints: { topic_id: /\d+/ }
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTopicSlowModeInterval < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :topics, :slow_mode_seconds, :integer, null: false, default: 0
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddLastPostedAtToTopicUser < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :topic_users, :last_posted_at, :datetime
|
||||
end
|
||||
end
|
|
@ -159,6 +159,19 @@ class PostCreator
|
|||
return false
|
||||
end
|
||||
|
||||
if @topic&.slow_mode_seconds.to_i > 0
|
||||
tu = TopicUser.find_by(user: @user, topic: @topic)
|
||||
|
||||
if tu&.last_posted_at
|
||||
threshold = tu.last_posted_at + @topic.slow_mode_seconds.seconds
|
||||
|
||||
if DateTime.now < threshold
|
||||
errors.add(:base, I18n.t(:slow_mode_enabled))
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless @topic.present? && (@opts[:skip_guardian] || guardian.can_create?(Post, @topic))
|
||||
errors.add(:base, I18n.t(:topic_not_found))
|
||||
return false
|
||||
|
@ -622,7 +635,8 @@ class PostCreator
|
|||
@topic.id,
|
||||
posted: true,
|
||||
last_read_post_number: @post.post_number,
|
||||
highest_seen_post_number: @post.post_number)
|
||||
highest_seen_post_number: @post.post_number,
|
||||
last_posted_at: Time.zone.now)
|
||||
|
||||
# assume it took us 5 seconds of reading time to make a post
|
||||
PostTiming.record_timing(topic_id: @post.topic_id,
|
||||
|
|
|
@ -130,6 +130,7 @@ module SvgSprite
|
|||
"heading",
|
||||
"heart",
|
||||
"home",
|
||||
"hourglass-end",
|
||||
"id-card",
|
||||
"info-circle",
|
||||
"italic",
|
||||
|
|
|
@ -722,6 +722,32 @@ describe PostCreator do
|
|||
expect(topic.word_count).to eq(6)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the topic is in slow mode' do
|
||||
before do
|
||||
one_day = 86400
|
||||
topic.update!(slow_mode_seconds: one_day)
|
||||
end
|
||||
|
||||
it 'fails if the user recently posted in this topic' do
|
||||
TopicUser.create!(user: user, topic: topic, last_posted_at: 10.minutes.ago)
|
||||
|
||||
post = creator.create
|
||||
|
||||
expect(post).to be_blank
|
||||
expect(creator.errors.count).to eq 1
|
||||
expect(creator.errors.messages[:base][0]).to match I18n.t(:slow_mode_enabled)
|
||||
end
|
||||
|
||||
it 'creates the topic if the user last post is older than the slow mode interval' do
|
||||
TopicUser.create!(user: user, topic: topic, last_posted_at: 5.days.ago)
|
||||
|
||||
post = creator.create
|
||||
|
||||
expect(post).to be_present
|
||||
expect(creator.errors.count).to be_zero
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'closed topic' do
|
||||
|
@ -1194,6 +1220,19 @@ describe PostCreator do
|
|||
topic_user = TopicUser.find_by(user_id: user.id, topic_id: pm.id)
|
||||
expect(topic_user.notification_level).to eq(3)
|
||||
end
|
||||
|
||||
it 'sets the last_posted_at timestamp to track the last time the user posted' do
|
||||
topic = Fabricate(:topic)
|
||||
|
||||
PostCreator.create(
|
||||
user,
|
||||
topic_id: topic.id,
|
||||
raw: "this is a test reply 123 123 ;)"
|
||||
)
|
||||
|
||||
topic_user = TopicUser.find_by(user_id: user.id, topic_id: topic.id)
|
||||
expect(topic_user.last_posted_at).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create!' do
|
||||
|
|
|
@ -3047,6 +3047,58 @@ RSpec.describe TopicsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#set_slow_mode' do
|
||||
context 'when not logged in' do
|
||||
it 'returns a forbidden response' do
|
||||
put "/t/#{topic.id}/slow_mode.json", params: {
|
||||
seconds: '3600'
|
||||
}
|
||||
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
end
|
||||
|
||||
context 'logged in as an admin' do
|
||||
it 'allows admins to set the slow mode interval' do
|
||||
sign_in(admin)
|
||||
|
||||
put "/t/#{topic.id}/slow_mode.json", params: {
|
||||
seconds: '3600'
|
||||
}
|
||||
|
||||
topic.reload
|
||||
expect(response.status).to eq(200)
|
||||
expect(topic.slow_mode_seconds).to eq(3600)
|
||||
end
|
||||
end
|
||||
|
||||
context 'logged in as a regular user' do
|
||||
it 'does nothing if the user is not TL4' do
|
||||
user.update!(trust_level: TrustLevel[3])
|
||||
sign_in(user)
|
||||
|
||||
put "/t/#{topic.id}/slow_mode.json", params: {
|
||||
seconds: '3600'
|
||||
}
|
||||
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
it 'allows TL4 users to set the slow mode interval' do
|
||||
user.update!(trust_level: TrustLevel[4])
|
||||
sign_in(user)
|
||||
|
||||
put "/t/#{topic.id}/slow_mode.json", params: {
|
||||
seconds: '3600'
|
||||
}
|
||||
|
||||
topic.reload
|
||||
expect(response.status).to eq(200)
|
||||
expect(topic.slow_mode_seconds).to eq(3600)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#invite' do
|
||||
describe 'when not logged in' do
|
||||
it "should return the right response" do
|
||||
|
|
Loading…
Reference in New Issue