mirror of
https://github.com/discourse/discourse.git
synced 2025-09-06 10:50:21 +08:00
FEATURE: auto-close topics based on last post
This commit is contained in:
parent
ac72b0bcf6
commit
5754e8dd0f
28 changed files with 242 additions and 146 deletions
|
@ -1,27 +1,40 @@
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
autoCloseValid: false,
|
autoCloseValid: false,
|
||||||
|
limited: false,
|
||||||
|
|
||||||
label: function() {
|
autoCloseUnits: function() {
|
||||||
return I18n.t( this.get('labelKey') || 'composer.auto_close_label' );
|
var key = this.get("limited") ? "composer.auto_close.limited.units"
|
||||||
}.property('labelKey'),
|
: "composer.auto_close.all.units";
|
||||||
|
return I18n.t(key);
|
||||||
|
}.property("limited"),
|
||||||
|
|
||||||
autoCloseChanged: function() {
|
autoCloseExamples: function() {
|
||||||
if( this.get('autoCloseTime') && this.get('autoCloseTime').length > 0 ) {
|
var key = this.get("limited") ? "composer.auto_close.limited.examples"
|
||||||
this.set('autoCloseTime', this.get('autoCloseTime').replace(/[^:\d-\s]/g, '') );
|
: "composer.auto_close.all.examples";
|
||||||
}
|
return I18n.t(key);
|
||||||
this.set('autoCloseValid', this.isAutoCloseValid());
|
}.property("limited"),
|
||||||
}.observes('autoCloseTime'),
|
|
||||||
|
|
||||||
isAutoCloseValid: function() {
|
_updateAutoCloseValid: function() {
|
||||||
if (this.get('autoCloseTime')) {
|
var isValid = this._isAutoCloseValid(this.get("autoCloseTime"), this.get("limited"));
|
||||||
var t = this.get('autoCloseTime').trim();
|
this.set("autoCloseValid", isValid);
|
||||||
if (t.match(/^[\d]{4}-[\d]{1,2}-[\d]{1,2} [\d]{1,2}:[\d]{2}/)) {
|
}.observes("autoCloseTime", "limited"),
|
||||||
return moment(t).isAfter(); // In the future
|
|
||||||
} else {
|
_isAutoCloseValid: function(autoCloseTime, limited) {
|
||||||
return (t.match(/^[\d]+$/) || t.match(/^[\d]{1,2}:[\d]{2}$/)) !== null;
|
var t = (autoCloseTime || "").trim();
|
||||||
}
|
if (t.length === 0) {
|
||||||
} else {
|
// "empty" is always valid
|
||||||
return true;
|
return true;
|
||||||
|
} else if (limited) {
|
||||||
|
// only # of hours in limited mode
|
||||||
|
return t.match(/^(\d+\.)?\d+$/);
|
||||||
|
} else {
|
||||||
|
if (t.match(/^\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{2}(\s?[AP]M)?$/i)) {
|
||||||
|
// timestamp must be in the future
|
||||||
|
return moment(t).isAfter();
|
||||||
|
} else {
|
||||||
|
// either # of hours or absolute time
|
||||||
|
return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||||
|
|
||||||
import ObjectController from 'discourse/controllers/object';
|
import ObjectController from 'discourse/controllers/object';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,24 +15,23 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||||
auto_close_invalid: Em.computed.not('auto_close_valid'),
|
auto_close_invalid: Em.computed.not('auto_close_valid'),
|
||||||
|
|
||||||
setAutoCloseTime: function() {
|
setAutoCloseTime: function() {
|
||||||
if( this.get('details.auto_close_at') ) {
|
var autoCloseTime = null;
|
||||||
var closeTime = new Date( this.get('details.auto_close_at') );
|
|
||||||
|
if (this.get("details.auto_close_based_on_last_post")) {
|
||||||
|
autoCloseTime = this.get("details.auto_close_hours");
|
||||||
|
} else if (this.get("details.auto_close_at")) {
|
||||||
|
var closeTime = new Date(this.get("details.auto_close_at"));
|
||||||
if (closeTime > new Date()) {
|
if (closeTime > new Date()) {
|
||||||
this.set('auto_close_time', moment(closeTime).format("YYYY-MM-DD HH:mm"));
|
autoCloseTime = moment(closeTime).format("YYYY-MM-DD HH:mm");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.set('details.auto_close_time', '');
|
|
||||||
}
|
}
|
||||||
}.observes('details.auto_close_at'),
|
|
||||||
|
this.set("auto_close_time", autoCloseTime);
|
||||||
|
}.observes("details.{auto_close_at,auto_close_hours}"),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
saveAutoClose: function() {
|
saveAutoClose: function() { this.setAutoClose(this.get("auto_close_time")); },
|
||||||
this.setAutoClose( this.get('auto_close_time') );
|
removeAutoClose: function() { this.setAutoClose(null); }
|
||||||
},
|
|
||||||
|
|
||||||
removeAutoClose: function() {
|
|
||||||
this.setAutoClose(null);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setAutoClose: function(time) {
|
setAutoClose: function(time) {
|
||||||
|
@ -43,16 +41,20 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||||
url: '/t/' + this.get('id') + '/autoclose',
|
url: '/t/' + this.get('id') + '/autoclose',
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: { auto_close_time: Discourse.Utilities.timestampFromAutocloseString(time) }
|
data: {
|
||||||
|
auto_close_time: time,
|
||||||
|
auto_close_based_on_last_post: this.get("details.auto_close_based_on_last_post"),
|
||||||
|
}
|
||||||
}).then(function(result){
|
}).then(function(result){
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
self.send('closeModal');
|
self.send('closeModal');
|
||||||
self.set('details.auto_close_at', result.auto_close_at);
|
self.set('details.auto_close_at', result.auto_close_at);
|
||||||
|
self.set('details.auto_close_hours', result.auto_close_hours);
|
||||||
} else {
|
} else {
|
||||||
bootbox.alert(I18n.t('composer.auto_close_error'), function() { self.send('showModal'); } );
|
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('showModal'); } );
|
||||||
}
|
}
|
||||||
}, function () {
|
}, function () {
|
||||||
bootbox.alert(I18n.t('composer.auto_close_error'), function() { self.send('showModal'); } );
|
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('showModal'); } );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -353,26 +353,6 @@ Discourse.Utilities = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
timestampFromAutocloseString: function(arg) {
|
|
||||||
if (!arg) return null;
|
|
||||||
if (arg.match(/^[\d]{4}-[\d]{1,2}-[\d]{1,2} [\d]{1,2}:[\d]{2}/)) {
|
|
||||||
return moment(arg).toJSON(); // moment will add the timezone
|
|
||||||
} else {
|
|
||||||
var matches = arg.match(/^([\d]{1,2}):([\d]{2})$/); // just the time HH:MM
|
|
||||||
if (matches) {
|
|
||||||
var now = moment(),
|
|
||||||
t = moment(new Date(now.year(), now.month(), now.date(), matches[1], matches[2]));
|
|
||||||
if (t.isAfter()) {
|
|
||||||
return t.toJSON();
|
|
||||||
} else {
|
|
||||||
return t.add('days', 1).toJSON();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return (arg === '' ? null : arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultHomepage: function() {
|
defaultHomepage: function() {
|
||||||
// the homepage is the first item of the 'top_menu' site setting
|
// the homepage is the first item of the 'top_menu' site setting
|
||||||
return Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
|
return Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
|
||||||
|
|
|
@ -163,7 +163,6 @@ Discourse.Post = Discourse.Model.extend({
|
||||||
title: this.get('title'),
|
title: this.get('title'),
|
||||||
image_sizes: this.get('imageSizes'),
|
image_sizes: this.get('imageSizes'),
|
||||||
target_usernames: this.get('target_usernames'),
|
target_usernames: this.get('target_usernames'),
|
||||||
auto_close_time: Discourse.Utilities.timestampFromAutocloseString(this.get('auto_close_time'))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var metaData = this.get('metaData');
|
var metaData = this.get('metaData');
|
||||||
|
|
|
@ -63,6 +63,7 @@ Discourse.Category = Discourse.Model.extend({
|
||||||
secure: this.get('secure'),
|
secure: this.get('secure'),
|
||||||
permissions: this.get('permissionsForUpdate'),
|
permissions: this.get('permissionsForUpdate'),
|
||||||
auto_close_hours: this.get('auto_close_hours'),
|
auto_close_hours: this.get('auto_close_hours'),
|
||||||
|
auto_close_based_on_last_post: this.get("auto_close_based_on_last_post"),
|
||||||
position: this.get('position'),
|
position: this.get('position'),
|
||||||
email_in: this.get('email_in'),
|
email_in: this.get('email_in'),
|
||||||
email_in_allow_strangers: this.get('email_in_allow_strangers'),
|
email_in_allow_strangers: this.get('email_in_allow_strangers'),
|
||||||
|
|
|
@ -521,7 +521,6 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
admin: currentUser.get('admin'),
|
admin: currentUser.get('admin'),
|
||||||
yours: true,
|
yours: true,
|
||||||
newPost: true,
|
newPost: true,
|
||||||
auto_close_time: Discourse.Utilities.timestampFromAutocloseString(this.get('auto_close_time'))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(post) {
|
if(post) {
|
||||||
|
@ -562,6 +561,7 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
// It's no longer a new post
|
// It's no longer a new post
|
||||||
createdPost.set('newPost', false);
|
createdPost.set('newPost', false);
|
||||||
topic.set('draft_sequence', result.draft_sequence);
|
topic.set('draft_sequence', result.draft_sequence);
|
||||||
|
topic.set('details.auto_close_at', result.topic_auto_close_at);
|
||||||
postStream.commitPost(createdPost);
|
postStream.commitPost(createdPost);
|
||||||
addedToStream = true;
|
addedToStream = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -760,6 +760,9 @@ Discourse.PostStream = Em.Object.extend({
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the auto_close_at value of the topic
|
||||||
|
this.set("topic.details.auto_close_at", post.get("topic_auto_close_at"));
|
||||||
|
|
||||||
post.set('topic', this.get('topic'));
|
post.set('topic', this.get('topic'));
|
||||||
postIdentityMap.set(post.get('id'), post);
|
postIdentityMap.set(post.get('id'), post);
|
||||||
|
|
||||||
|
@ -822,7 +825,6 @@ Discourse.PostStream = Em.Object.extend({
|
||||||
@returns {Promise} a promise that will resolve to the posts in the order requested.
|
@returns {Promise} a promise that will resolve to the posts in the order requested.
|
||||||
**/
|
**/
|
||||||
loadIntoIdentityMap: function(postIds) {
|
loadIntoIdentityMap: function(postIds) {
|
||||||
|
|
||||||
// If we don't want any posts, return a promise that resolves right away
|
// If we don't want any posts, return a promise that resolves right away
|
||||||
if (Em.isEmpty(postIds)) {
|
if (Em.isEmpty(postIds)) {
|
||||||
return Ember.RSVP.resolve();
|
return Ember.RSVP.resolve();
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
<div class="auto-close-fields">
|
<div class="auto-close-fields">
|
||||||
<div>
|
<div>
|
||||||
<i class="fa fa-clock-o"></i>
|
<label>
|
||||||
{{label}}
|
{{fa-icon clock-o}}
|
||||||
{{text-field value=autoCloseTime}}
|
{{i18n composer.auto_close.label}}
|
||||||
{{i18n composer.auto_close_units}}
|
{{text-field value=autoCloseTime}}
|
||||||
|
{{autoCloseUnits}}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="examples">
|
<div class="examples">
|
||||||
{{i18n composer.auto_close_examples}}
|
{{autoCloseExamples}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox" name="autoCloseBasedOnLastPost" checked=autoCloseBasedOnLastPost}}
|
||||||
|
{{i18n composer.auto_close.based_on_last_post}}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<form {{action "saveAutoClose" on="submit"}}>
|
<form {{action "saveAutoClose" on="submit"}}>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{{auto-close-form autoCloseTime=auto_close_time autoCloseValid=auto_close_valid}}
|
{{auto-close-form autoCloseTime=auto_close_time
|
||||||
|
autoCloseValid=auto_close_valid
|
||||||
|
autoCloseBasedOnLastPost=details.auto_close_based_on_last_post
|
||||||
|
limited=details.auto_close_based_on_last_post }}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class='btn btn-primary' type='submit' {{bind-attr disabled="auto_close_invalid"}}>{{i18n topic.auto_close_save}}</button>
|
<button class='btn btn-primary' type='submit' {{bind-attr disabled="auto_close_invalid"}}>{{i18n topic.auto_close_save}}</button>
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
<section class='field'>
|
<section class='field'>
|
||||||
<div class="auto-close-fields">
|
{{auto-close-form autoCloseTime=auto_close_hours
|
||||||
<div>
|
autoCloseBasedOnLastPost=auto_close_based_on_last_post
|
||||||
<i class="fa fa-clock-o"></i>
|
limited="true" }}
|
||||||
{{i18n category.auto_close_label}}
|
|
||||||
{{text-field value=auto_close_hours}}
|
|
||||||
{{i18n category.auto_close_units}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class='field'>
|
<section class='field'>
|
||||||
|
|
|
@ -93,18 +93,27 @@ div.ac-wrap {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.auto-close-fields {
|
.auto-close-fields {
|
||||||
|
div:not(:first-child) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
input {
|
input {
|
||||||
|
text-align: center;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
.examples {
|
.examples {
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: $primary;
|
color: $primary;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-auto-close-modal {
|
.edit-auto-close-modal {
|
||||||
|
.btn.pull-right {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
form {
|
form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -118,3 +127,11 @@ div.ac-wrap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-category-modal {
|
||||||
|
.auto-close-fields {
|
||||||
|
input[type=text] {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -354,7 +354,6 @@ class ApplicationController < ActionController::Base
|
||||||
def render_post_json(post, add_raw=true)
|
def render_post_json(post, add_raw=true)
|
||||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||||
post_serializer.add_raw = add_raw
|
post_serializer.add_raw = add_raw
|
||||||
post_serializer.topic_slug = post.topic.slug if post.topic.present?
|
|
||||||
|
|
||||||
counts = PostAction.counts_for([post], current_user)
|
counts = PostAction.counts_for([post], current_user)
|
||||||
if counts && counts = counts[post.id]
|
if counts && counts = counts[post.id]
|
||||||
|
|
|
@ -75,15 +75,20 @@ class CategoriesController < ApplicationController
|
||||||
|
|
||||||
def update
|
def update
|
||||||
guardian.ensure_can_edit!(@category)
|
guardian.ensure_can_edit!(@category)
|
||||||
json_result(@category, serializer: CategorySerializer) { |cat|
|
|
||||||
|
json_result(@category, serializer: CategorySerializer) do |cat|
|
||||||
|
|
||||||
cat.move_to(category_params[:position].to_i) if category_params[:position]
|
cat.move_to(category_params[:position].to_i) if category_params[:position]
|
||||||
|
|
||||||
if category_params.key? :email_in and category_params[:email_in].length == 0
|
if category_params.key? :email_in and category_params[:email_in].length == 0
|
||||||
# properly null the value so the database constrain doesn't catch us
|
# properly null the value so the database constrain doesn't catch us
|
||||||
category_params[:email_in] = nil
|
category_params[:email_in] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
category_params.delete(:position)
|
category_params.delete(:position)
|
||||||
|
|
||||||
cat.update_attributes(category_params)
|
cat.update_attributes(category_params)
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_notifications
|
def set_notifications
|
||||||
|
@ -125,6 +130,7 @@ class CategoriesController < ApplicationController
|
||||||
:email_in_allow_strangers,
|
:email_in_allow_strangers,
|
||||||
:parent_category_id,
|
:parent_category_id,
|
||||||
:auto_close_hours,
|
:auto_close_hours,
|
||||||
|
:auto_close_based_on_last_post,
|
||||||
:logo_url,
|
:logo_url,
|
||||||
:background_url,
|
:background_url,
|
||||||
:allow_badges,
|
:allow_badges,
|
||||||
|
|
|
@ -79,7 +79,6 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
else
|
else
|
||||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||||
post_serializer.topic_slug = post.topic.slug if post.topic.present?
|
|
||||||
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
|
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
|
||||||
[true, MultiJson.dump(post_serializer)]
|
[true, MultiJson.dump(post_serializer)]
|
||||||
end
|
end
|
||||||
|
@ -132,7 +131,6 @@ class PostsController < ApplicationController
|
||||||
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
|
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
|
||||||
link_counts = TopicLink.counts_for(guardian,post.topic, [post])
|
link_counts = TopicLink.counts_for(guardian,post.topic, [post])
|
||||||
post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
|
post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
|
||||||
post_serializer.topic_slug = post.topic.slug if post.topic.present?
|
|
||||||
|
|
||||||
result = {post: post_serializer.as_json}
|
result = {post: post_serializer.as_json}
|
||||||
if revisor.category_changed.present?
|
if revisor.category_changed.present?
|
||||||
|
@ -346,7 +344,6 @@ class PostsController < ApplicationController
|
||||||
:category,
|
:category,
|
||||||
:target_usernames,
|
:target_usernames,
|
||||||
:reply_to_post_number,
|
:reply_to_post_number,
|
||||||
:auto_close_time,
|
|
||||||
:auto_track
|
:auto_track
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -178,12 +178,20 @@ class TopicsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def autoclose
|
def autoclose
|
||||||
raise Discourse::InvalidParameters.new(:auto_close_time) unless params.has_key?(:auto_close_time)
|
params.permit(:auto_close_time)
|
||||||
|
params.require(:auto_close_based_on_last_post)
|
||||||
|
|
||||||
topic = Topic.find_by(id: params[:topic_id].to_i)
|
topic = Topic.find_by(id: params[:topic_id].to_i)
|
||||||
guardian.ensure_can_moderate!(topic)
|
guardian.ensure_can_moderate!(topic)
|
||||||
|
|
||||||
|
topic.auto_close_based_on_last_post = params[:auto_close_based_on_last_post]
|
||||||
topic.set_auto_close(params[:auto_close_time], current_user)
|
topic.set_auto_close(params[:auto_close_time], current_user)
|
||||||
|
|
||||||
if topic.save
|
if topic.save
|
||||||
render json: success_json.merge!(auto_close_at: topic.auto_close_at)
|
render json: success_json.merge!({
|
||||||
|
auto_close_at: topic.auto_close_at,
|
||||||
|
auto_close_hours: topic.auto_close_hours
|
||||||
|
})
|
||||||
else
|
else
|
||||||
render_json_error(topic)
|
render_json_error(topic)
|
||||||
end
|
end
|
||||||
|
|
|
@ -170,7 +170,8 @@ SQL
|
||||||
def create_category_definition
|
def create_category_definition
|
||||||
t = Topic.new(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now, category_id: id)
|
t = Topic.new(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now, category_id: id)
|
||||||
t.skip_callbacks = true
|
t.skip_callbacks = true
|
||||||
t.auto_close_hours = nil
|
t.ignore_category_auto_close = true
|
||||||
|
t.set_auto_close(nil)
|
||||||
t.save!(validate: false)
|
t.save!(validate: false)
|
||||||
update_column(:topic_id, t.id)
|
update_column(:topic_id, t.id)
|
||||||
t.posts.create(raw: post_template, user: user)
|
t.posts.create(raw: post_template, user: user)
|
||||||
|
|
|
@ -147,10 +147,14 @@ class Topic < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_accessor :ignore_category_auto_close
|
||||||
|
|
||||||
before_create do
|
before_create do
|
||||||
self.bumped_at ||= Time.now
|
self.bumped_at ||= Time.now
|
||||||
self.last_post_user_id ||= user_id
|
self.last_post_user_id ||= user_id
|
||||||
|
|
||||||
if !@ignore_category_auto_close and self.category and self.category.auto_close_hours and self.auto_close_at.nil?
|
if !@ignore_category_auto_close and self.category and self.category.auto_close_hours and self.auto_close_at.nil?
|
||||||
|
self.auto_close_based_on_last_post = self.category.auto_close_based_on_last_post
|
||||||
set_auto_close(self.category.auto_close_hours)
|
set_auto_close(self.category.auto_close_hours)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -158,7 +162,6 @@ class Topic < ActiveRecord::Base
|
||||||
attr_accessor :skip_callbacks
|
attr_accessor :skip_callbacks
|
||||||
|
|
||||||
after_create do
|
after_create do
|
||||||
|
|
||||||
unless skip_callbacks
|
unless skip_callbacks
|
||||||
changed_to_category(category)
|
changed_to_category(category)
|
||||||
if archetype == Archetype.private_message
|
if archetype == Archetype.private_message
|
||||||
|
@ -167,22 +170,18 @@ class Topic < ActiveRecord::Base
|
||||||
DraftSequence.next!(user, Draft::NEW_TOPIC)
|
DraftSequence.next!(user, Draft::NEW_TOPIC)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before_save do
|
before_save do
|
||||||
|
|
||||||
unless skip_callbacks
|
unless skip_callbacks
|
||||||
if (auto_close_at_changed? and !auto_close_at_was.nil?) or (auto_close_user_id_changed? and auto_close_at)
|
if (auto_close_at_changed? and !auto_close_at_was.nil?) or (auto_close_user_id_changed? and auto_close_at)
|
||||||
self.auto_close_started_at ||= Time.zone.now if auto_close_at
|
self.auto_close_started_at ||= Time.zone.now if auto_close_at
|
||||||
Jobs.cancel_scheduled_job(:close_topic, {topic_id: id})
|
Jobs.cancel_scheduled_job(:close_topic, {topic_id: id})
|
||||||
true
|
|
||||||
end
|
end
|
||||||
if category_id.nil? && (archetype.nil? || archetype == Archetype.default)
|
if category_id.nil? && (archetype.nil? || archetype == Archetype.default)
|
||||||
self.category_id = SiteSetting.uncategorized_category_id
|
self.category_id = SiteSetting.uncategorized_category_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
after_save do
|
after_save do
|
||||||
|
@ -193,7 +192,6 @@ class Topic < ActiveRecord::Base
|
||||||
Jobs.enqueue_at(auto_close_at, :close_topic, {topic_id: id, user_id: auto_close_user_id || user_id})
|
Jobs.enqueue_at(auto_close_at, :close_topic, {topic_id: id, user_id: auto_close_user_id || user_id})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO move into PostRevisor or TopicRevisor
|
# TODO move into PostRevisor or TopicRevisor
|
||||||
|
@ -745,11 +743,6 @@ class Topic < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def auto_close_hours=(num_hours)
|
|
||||||
@ignore_category_auto_close = true
|
|
||||||
set_auto_close( num_hours )
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.auto_close
|
def self.auto_close
|
||||||
Topic.where("NOT closed AND auto_close_at < ? AND auto_close_user_id IS NOT NULL", 1.minute.ago).each do |t|
|
Topic.where("NOT closed AND auto_close_at < ? AND auto_close_user_id IS NOT NULL", 1.minute.ago).each do |t|
|
||||||
t.auto_close
|
t.auto_close
|
||||||
|
@ -773,28 +766,51 @@ class Topic < ActiveRecord::Base
|
||||||
# * A timestamp with timezone in JSON format. (e.g., "2013-11-26T21:00:00.000Z")
|
# * A timestamp with timezone in JSON format. (e.g., "2013-11-26T21:00:00.000Z")
|
||||||
# * nil, to prevent the topic from automatically closing.
|
# * nil, to prevent the topic from automatically closing.
|
||||||
def set_auto_close(arg, by_user=nil)
|
def set_auto_close(arg, by_user=nil)
|
||||||
if arg.is_a?(String) && matches = /^([\d]{1,2}):([\d]{1,2})$/.match(arg.strip)
|
self.auto_close_hours = nil
|
||||||
now = Time.zone.now
|
|
||||||
self.auto_close_at = Time.zone.local(now.year, now.month, now.day, matches[1].to_i, matches[2].to_i)
|
if self.auto_close_based_on_last_post
|
||||||
self.auto_close_at += 1.day if self.auto_close_at < now
|
num_hours = arg.to_f
|
||||||
elsif arg.is_a?(String) && arg.include?('-') && timestamp = Time.zone.parse(arg)
|
if num_hours > 0
|
||||||
self.auto_close_at = timestamp
|
last_post_created_at = self.ordered_posts.last.try(:created_at) || Time.zone.now
|
||||||
self.errors.add(:auto_close_at, :invalid) if timestamp < Time.zone.now
|
self.auto_close_at = last_post_created_at + num_hours.hours
|
||||||
|
self.auto_close_hours = num_hours
|
||||||
|
else
|
||||||
|
self.auto_close_at = nil
|
||||||
|
end
|
||||||
else
|
else
|
||||||
num_hours = arg.to_i
|
if arg.is_a?(String) && m = /^(\d{1,2}):(\d{2})(?:\s*[AP]M)?$/i.match(arg.strip)
|
||||||
self.auto_close_at = (num_hours > 0 ? num_hours.hours.from_now : nil)
|
now = Time.zone.now
|
||||||
|
self.auto_close_at = Time.zone.local(now.year, now.month, now.day, m[1].to_i, m[2].to_i)
|
||||||
|
self.auto_close_at += 1.day if self.auto_close_at < now
|
||||||
|
elsif arg.is_a?(String) && arg.include?("-") && timestamp = Time.zone.parse(arg)
|
||||||
|
self.auto_close_at = timestamp
|
||||||
|
self.errors.add(:auto_close_at, :invalid) if timestamp < Time.zone.now
|
||||||
|
else
|
||||||
|
num_hours = arg.to_f
|
||||||
|
if num_hours > 0
|
||||||
|
self.auto_close_at = num_hours.hours.from_now
|
||||||
|
self.auto_close_hours = num_hours
|
||||||
|
else
|
||||||
|
self.auto_close_at = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless self.auto_close_at.nil?
|
if self.auto_close_at.nil?
|
||||||
self.auto_close_started_at ||= Time.zone.now
|
self.auto_close_started_at = nil
|
||||||
if by_user && by_user.staff?
|
else
|
||||||
|
if self.auto_close_based_on_last_post
|
||||||
|
self.auto_close_started_at = Time.zone.now
|
||||||
|
else
|
||||||
|
self.auto_close_started_at ||= Time.zone.now
|
||||||
|
end
|
||||||
|
if by_user.try(:staff?)
|
||||||
self.auto_close_user = by_user
|
self.auto_close_user = by_user
|
||||||
else
|
else
|
||||||
self.auto_close_user ||= (self.user.staff? ? self.user : Discourse.system_user)
|
self.auto_close_user ||= (self.user.staff? ? self.user : Discourse.system_user)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
self.auto_close_started_at = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ class CategorySerializer < BasicCategorySerializer
|
||||||
attributes :read_restricted,
|
attributes :read_restricted,
|
||||||
:available_groups,
|
:available_groups,
|
||||||
:auto_close_hours,
|
:auto_close_hours,
|
||||||
|
:auto_close_based_on_last_post,
|
||||||
:group_permissions,
|
:group_permissions,
|
||||||
:position,
|
:position,
|
||||||
:email_in,
|
:email_in,
|
||||||
|
@ -20,7 +21,7 @@ class CategorySerializer < BasicCategorySerializer
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
if perms.length == 0 && !object.read_restricted
|
if perms.length == 0 && !object.read_restricted
|
||||||
perms << {permission_type: CategoryGroup.permission_types[:full], group_name: :everyone}
|
perms << { permission_type: CategoryGroup.permission_types[:full], group_name: :everyone }
|
||||||
end
|
end
|
||||||
perms
|
perms
|
||||||
end
|
end
|
||||||
|
@ -30,7 +31,6 @@ class CategorySerializer < BasicCategorySerializer
|
||||||
Group.order(:name).pluck(:name) - group_permissions.map{|g| g[:group_name]}
|
Group.order(:name).pluck(:name) - group_permissions.map{|g| g[:group_name]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def can_delete
|
def can_delete
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
class PostSerializer < BasicPostSerializer
|
class PostSerializer < BasicPostSerializer
|
||||||
|
|
||||||
# To pass in additional information we might need
|
# To pass in additional information we might need
|
||||||
attr_accessor :topic_slug,
|
attr_accessor :topic_view,
|
||||||
:topic_view,
|
|
||||||
:parent_post,
|
:parent_post,
|
||||||
:add_raw,
|
:add_raw,
|
||||||
:single_post_link_counts,
|
:single_post_link_counts,
|
||||||
|
@ -20,8 +19,9 @@ class PostSerializer < BasicPostSerializer
|
||||||
:reads,
|
:reads,
|
||||||
:score,
|
:score,
|
||||||
:yours,
|
:yours,
|
||||||
:topic_slug,
|
|
||||||
:topic_id,
|
:topic_id,
|
||||||
|
:topic_slug,
|
||||||
|
:topic_auto_close_at,
|
||||||
:display_username,
|
:display_username,
|
||||||
:primary_group_name,
|
:primary_group_name,
|
||||||
:version,
|
:version,
|
||||||
|
@ -53,6 +53,14 @@ class PostSerializer < BasicPostSerializer
|
||||||
:static_doc,
|
:static_doc,
|
||||||
:via_email
|
:via_email
|
||||||
|
|
||||||
|
def topic_slug
|
||||||
|
object.try(:topic).try(:slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
def topic_auto_close_at
|
||||||
|
object.try(:topic).try(:auto_close_at)
|
||||||
|
end
|
||||||
|
|
||||||
def moderator?
|
def moderator?
|
||||||
!!(object.try(:user).try(:moderator?))
|
!!(object.try(:user).try(:moderator?))
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,6 @@ module PostStreamSerializerMixin
|
||||||
object.posts.each_with_index do |p, idx|
|
object.posts.each_with_index do |p, idx|
|
||||||
highest_number_in_posts = p.post_number if p.post_number > highest_number_in_posts
|
highest_number_in_posts = p.post_number if p.post_number > highest_number_in_posts
|
||||||
ps = PostSerializer.new(p, scope: scope, root: false)
|
ps = PostSerializer.new(p, scope: scope, root: false)
|
||||||
ps.topic_slug = object.topic.slug
|
|
||||||
ps.topic_view = object
|
ps.topic_view = object
|
||||||
p.topic = object.topic
|
p.topic = object.topic
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
def details
|
def details
|
||||||
result = {
|
result = {
|
||||||
auto_close_at: object.topic.auto_close_at,
|
auto_close_at: object.topic.auto_close_at,
|
||||||
|
auto_close_hours: object.topic.auto_close_hours,
|
||||||
|
auto_close_based_on_last_post: object.topic.auto_close_based_on_last_post,
|
||||||
created_by: BasicUserSerializer.new(object.topic.user, scope: scope, root: false),
|
created_by: BasicUserSerializer.new(object.topic.user, scope: scope, root: false),
|
||||||
last_poster: BasicUserSerializer.new(object.topic.last_poster, scope: scope, root: false)
|
last_poster: BasicUserSerializer.new(object.topic.last_poster, scope: scope, root: false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -705,10 +705,16 @@ en:
|
||||||
toggler: "hide or show the composer panel"
|
toggler: "hide or show the composer panel"
|
||||||
|
|
||||||
admin_options_title: "Optional staff settings for this topic"
|
admin_options_title: "Optional staff settings for this topic"
|
||||||
auto_close_label: "Auto-close topic time:"
|
auto_close:
|
||||||
auto_close_units: "(# of hours, a time, or a timestamp)"
|
label: "Auto-close topic time:"
|
||||||
auto_close_examples: 'enter absolute time or number of hours — 24, 17:00, 2013-11-22 14:00'
|
error: "Please enter a valid value."
|
||||||
auto_close_error: "Please enter a valid value."
|
based_on_last_post: "Auto-close based on last post."
|
||||||
|
all:
|
||||||
|
units: "(# of hours, a time or a timestamp)"
|
||||||
|
examples: 'Enter number of hours (24), absolute time (17:30) or timestamp (2013-11-22 14:00).'
|
||||||
|
limited:
|
||||||
|
units: "(# of hours)"
|
||||||
|
examples: 'Enter number of hours (24).'
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
title: "notifications of @name mentions, replies to your posts and topics, private messages, etc"
|
title: "notifications of @name mentions, replies to your posts and topics, private messages, etc"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddAutoCloseBasedOnLastPostAndAutoCloseHoursToTopics < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :topics, :auto_close_based_on_last_post, :boolean, default: false
|
||||||
|
add_column :topics, :auto_close_hours, :float
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddAutoCloseBasedOnLastPostToCategories < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :categories, :auto_close_based_on_last_post, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -76,6 +76,7 @@ class PostCreator
|
||||||
store_unique_post_key
|
store_unique_post_key
|
||||||
track_topic
|
track_topic
|
||||||
update_topic_stats
|
update_topic_stats
|
||||||
|
update_topic_auto_close
|
||||||
update_user_counts
|
update_user_counts
|
||||||
create_embedded_topic
|
create_embedded_topic
|
||||||
|
|
||||||
|
@ -208,6 +209,12 @@ class PostCreator
|
||||||
@topic.update_attributes(attrs)
|
@topic.update_attributes(attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_topic_auto_close
|
||||||
|
if @topic.auto_close_based_on_last_post && @topic.auto_close_hours
|
||||||
|
@topic.set_auto_close(@topic.auto_close_hours).save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def setup_post
|
def setup_post
|
||||||
@opts[:raw] = TextCleaner.normalize_whitespaces(@opts[:raw]).gsub(/\s+\z/, "")
|
@opts[:raw] = TextCleaner.normalize_whitespaces(@opts[:raw]).gsub(/\s+\z/, "")
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,31 @@ describe PostCreator do
|
||||||
topic.reload
|
topic.reload
|
||||||
}.to_not change { topic.excerpt }
|
}.to_not change { topic.excerpt }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "topic's auto close" do
|
||||||
|
|
||||||
|
it "doesn't update topic's auto close when it's not based on last post" do
|
||||||
|
auto_close_time = 1.day.from_now
|
||||||
|
topic = Fabricate(:topic, auto_close_at: auto_close_time, auto_close_hours: 12)
|
||||||
|
|
||||||
|
PostCreator.new(topic.user, topic_id: topic.id, raw: "this is a second post").create
|
||||||
|
topic.reload
|
||||||
|
|
||||||
|
topic.auto_close_at.should be_within(1.second).of(auto_close_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates topic's auto close date when it's based on last post" do
|
||||||
|
auto_close_time = 1.day.from_now
|
||||||
|
topic = Fabricate(:topic, auto_close_at: auto_close_time, auto_close_hours: 12, auto_close_based_on_last_post: true)
|
||||||
|
|
||||||
|
PostCreator.new(topic.user, topic_id: topic.id, raw: "this is a second post").create
|
||||||
|
topic.reload
|
||||||
|
|
||||||
|
topic.auto_close_at.should_not be_within(1.second).of(auto_close_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when auto-close param is given' do
|
context 'when auto-close param is given' do
|
||||||
|
@ -526,4 +551,3 @@ describe PostCreator do
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -865,12 +865,14 @@ describe TopicsController do
|
||||||
describe 'autoclose' do
|
describe 'autoclose' do
|
||||||
|
|
||||||
it 'needs you to be logged in' do
|
it 'needs you to be logged in' do
|
||||||
lambda { xhr :put, :autoclose, topic_id: 99, auto_close_time: '24'}.should raise_error(Discourse::NotLoggedIn)
|
-> {
|
||||||
|
xhr :put, :autoclose, topic_id: 99, auto_close_time: '24', auto_close_based_on_last_post: false
|
||||||
|
}.should raise_error(Discourse::NotLoggedIn)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'needs you to be an admin or mod' do
|
it 'needs you to be an admin or mod' do
|
||||||
user = log_in
|
user = log_in
|
||||||
xhr :put, :autoclose, topic_id: 99, auto_close_time: '24'
|
xhr :put, :autoclose, topic_id: 99, auto_close_time: '24', auto_close_based_on_last_post: false
|
||||||
response.should be_forbidden
|
response.should be_forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -880,16 +882,17 @@ describe TopicsController do
|
||||||
@topic = Fabricate(:topic, user: @admin)
|
@topic = Fabricate(:topic, user: @admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can set a topic's auto close time" do
|
it "can set a topic's auto close time and 'based on last post' property" do
|
||||||
Topic.any_instance.expects(:set_auto_close).with("24", @admin)
|
Topic.any_instance.expects(:set_auto_close).with("24", @admin)
|
||||||
xhr :put, :autoclose, topic_id: @topic.id, auto_close_time: '24'
|
xhr :put, :autoclose, topic_id: @topic.id, auto_close_time: '24', auto_close_based_on_last_post: true
|
||||||
json = ::JSON.parse(response.body)
|
json = ::JSON.parse(response.body)
|
||||||
json.should have_key('auto_close_at')
|
json.should have_key('auto_close_at')
|
||||||
|
json.should have_key('auto_close_hours')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can remove a topic's auto close time" do
|
it "can remove a topic's auto close time" do
|
||||||
Topic.any_instance.expects(:set_auto_close).with(nil, anything)
|
Topic.any_instance.expects(:set_auto_close).with(nil, anything)
|
||||||
xhr :put, :autoclose, topic_id: @topic.id, auto_close_time: nil
|
xhr :put, :autoclose, topic_id: @topic.id, auto_close_time: nil, auto_close_based_on_last_post: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -990,7 +990,8 @@ describe Topic do
|
||||||
it 'queues a job to close the topic' do
|
it 'queues a job to close the topic' do
|
||||||
Timecop.freeze(now) do
|
Timecop.freeze(now) do
|
||||||
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
||||||
Fabricate(:topic, auto_close_hours: 7, user: Fabricate(:admin))
|
topic = Fabricate(:topic, user: Fabricate(:admin))
|
||||||
|
topic.set_auto_close(7).save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -999,7 +1000,8 @@ describe Topic do
|
||||||
Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
|
Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
|
||||||
job_args[:user_id] == topic_creator.id
|
job_args[:user_id] == topic_creator.id
|
||||||
end
|
end
|
||||||
Fabricate(:topic, auto_close_hours: 7, user: topic_creator)
|
topic = Fabricate(:topic, user: topic_creator)
|
||||||
|
topic.set_auto_close(7).save
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'when auto_close_user_id is set, it will use it as the topic closer' do
|
it 'when auto_close_user_id is set, it will use it as the topic closer' do
|
||||||
|
@ -1008,19 +1010,22 @@ describe Topic do
|
||||||
Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
|
Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
|
||||||
job_args[:user_id] == topic_closer.id
|
job_args[:user_id] == topic_closer.id
|
||||||
end
|
end
|
||||||
Fabricate(:topic, auto_close_hours: 7, auto_close_user: topic_closer, user: topic_creator)
|
topic = Fabricate(:topic, user: topic_creator)
|
||||||
|
topic.set_auto_close(7, topic_closer).save
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ignores the category's default auto-close" do
|
it "ignores the category's default auto-close" do
|
||||||
Timecop.freeze(now) do
|
Timecop.freeze(now) do
|
||||||
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
||||||
Fabricate(:topic, auto_close_hours: 7, user: Fabricate(:admin), category_id: Fabricate(:category, auto_close_hours: 2).id)
|
topic = Fabricate(:topic, user: Fabricate(:admin), ignore_category_auto_close: true, category_id: Fabricate(:category, auto_close_hours: 2).id)
|
||||||
|
topic.set_auto_close(7).save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the time when auto_close timer starts' do
|
it 'sets the time when auto_close timer starts' do
|
||||||
Timecop.freeze(now) do
|
Timecop.freeze(now) do
|
||||||
topic = Fabricate(:topic, auto_close_hours: 7, user: Fabricate(:admin))
|
topic = Fabricate(:topic, user: Fabricate(:admin))
|
||||||
|
topic.set_auto_close(7).save
|
||||||
expect(topic.auto_close_started_at).to eq(now)
|
expect(topic.auto_close_started_at).to eq(now)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1124,25 +1129,9 @@ describe Topic do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "auto_close_hours=" do
|
|
||||||
subject(:topic) { Fabricate.build(:topic) }
|
|
||||||
|
|
||||||
it 'can take a number' do
|
|
||||||
Timecop.freeze(now) do
|
|
||||||
topic.auto_close_hours = 2
|
|
||||||
topic.auto_close_at.should be_within_one_second_of(2.hours.from_now)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can take nil' do
|
|
||||||
topic.auto_close_hours = nil
|
|
||||||
topic.auto_close_at.should == nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'set_auto_close' do
|
describe 'set_auto_close' do
|
||||||
let(:topic) { Fabricate.build(:topic) }
|
let(:topic) { Fabricate.build(:topic) }
|
||||||
let(:closing_topic) { Fabricate.build(:topic, auto_close_hours: 5) }
|
let(:closing_topic) { Fabricate.build(:topic, auto_close_hours: 5, auto_close_at: 5.hours.from_now, auto_close_started_at: 5.hours.from_now) }
|
||||||
let(:admin) { Fabricate.build(:user, id: 123) }
|
let(:admin) { Fabricate.build(:user, id: 123) }
|
||||||
|
|
||||||
before { Discourse.stubs(:system_user).returns(admin) }
|
before { Discourse.stubs(:system_user).returns(admin) }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue