Skip to content

Commit

Permalink
FEATURE: auto-close topics based on last post
Browse files Browse the repository at this point in the history
  • Loading branch information
ZogStriP committed Oct 10, 2014
1 parent ac72b0b commit 5754e8d
Show file tree
Hide file tree
Showing 28 changed files with 241 additions and 145 deletions.
47 changes: 30 additions & 17 deletions app/assets/javascripts/discourse/components/auto-close-form.js.es6
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
export default Ember.Component.extend({
autoCloseValid: false,
limited: false,

label: function() {
return I18n.t( this.get('labelKey') || 'composer.auto_close_label' );
}.property('labelKey'),
autoCloseUnits: function() {
var key = this.get("limited") ? "composer.auto_close.limited.units"
: "composer.auto_close.all.units";
return I18n.t(key);
}.property("limited"),

autoCloseChanged: function() {
if( this.get('autoCloseTime') && this.get('autoCloseTime').length > 0 ) {
this.set('autoCloseTime', this.get('autoCloseTime').replace(/[^:\d-\s]/g, '') );
}
this.set('autoCloseValid', this.isAutoCloseValid());
}.observes('autoCloseTime'),
autoCloseExamples: function() {
var key = this.get("limited") ? "composer.auto_close.limited.examples"
: "composer.auto_close.all.examples";
return I18n.t(key);
}.property("limited"),

_updateAutoCloseValid: function() {
var isValid = this._isAutoCloseValid(this.get("autoCloseTime"), this.get("limited"));
this.set("autoCloseValid", isValid);
}.observes("autoCloseTime", "limited"),

isAutoCloseValid: function() {
if (this.get('autoCloseTime')) {
var t = this.get('autoCloseTime').trim();
if (t.match(/^[\d]{4}-[\d]{1,2}-[\d]{1,2} [\d]{1,2}:[\d]{2}/)) {
return moment(t).isAfter(); // In the future
_isAutoCloseValid: function(autoCloseTime, limited) {
var t = (autoCloseTime || "").trim();
if (t.length === 0) {
// "empty" is always valid
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 {
return (t.match(/^[\d]+$/) || t.match(/^[\d]{1,2}:[\d]{2}$/)) !== null;
// either # of hours or absolute time
return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null;
}
} else {
return true;
}
}
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';

import ObjectController from 'discourse/controllers/object';

/**
Expand All @@ -16,24 +15,23 @@ export default ObjectController.extend(ModalFunctionality, {
auto_close_invalid: Em.computed.not('auto_close_valid'),

setAutoCloseTime: function() {
if( this.get('details.auto_close_at') ) {
var closeTime = new Date( this.get('details.auto_close_at') );
var autoCloseTime = null;

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()) {
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'),

actions: {
saveAutoClose: function() {
this.setAutoClose( this.get('auto_close_time') );
},
this.set("auto_close_time", autoCloseTime);
}.observes("details.{auto_close_at,auto_close_hours}"),

removeAutoClose: function() {
this.setAutoClose(null);
}
actions: {
saveAutoClose: function() { this.setAutoClose(this.get("auto_close_time")); },
removeAutoClose: function() { this.setAutoClose(null); }
},

setAutoClose: function(time) {
Expand All @@ -43,16 +41,20 @@ export default ObjectController.extend(ModalFunctionality, {
url: '/t/' + this.get('id') + '/autoclose',
type: 'PUT',
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){
if (result.success) {
self.send('closeModal');
self.set('details.auto_close_at', result.auto_close_at);
self.set('details.auto_close_hours', result.auto_close_hours);
} 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 () {
bootbox.alert(I18n.t('composer.auto_close_error'), function() { self.send('showModal'); } );
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('showModal'); } );
});
}

Expand Down
20 changes: 0 additions & 20 deletions app/assets/javascripts/discourse/lib/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
// the homepage is the first item of the 'top_menu' site setting
return Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
Expand Down
1 change: 0 additions & 1 deletion app/assets/javascripts/discourse/models/_post.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ Discourse.Post = Discourse.Model.extend({
title: this.get('title'),
image_sizes: this.get('imageSizes'),
target_usernames: this.get('target_usernames'),
auto_close_time: Discourse.Utilities.timestampFromAutocloseString(this.get('auto_close_time'))
};

var metaData = this.get('metaData');
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/discourse/models/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Discourse.Category = Discourse.Model.extend({
secure: this.get('secure'),
permissions: this.get('permissionsForUpdate'),
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'),
email_in: this.get('email_in'),
email_in_allow_strangers: this.get('email_in_allow_strangers'),
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/discourse/models/composer.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,6 @@ Discourse.Composer = Discourse.Model.extend({
admin: currentUser.get('admin'),
yours: true,
newPost: true,
auto_close_time: Discourse.Utilities.timestampFromAutocloseString(this.get('auto_close_time'))
});

if(post) {
Expand Down Expand Up @@ -562,6 +561,7 @@ Discourse.Composer = Discourse.Model.extend({
// It's no longer a new post
createdPost.set('newPost', false);
topic.set('draft_sequence', result.draft_sequence);
topic.set('details.auto_close_at', result.topic_auto_close_at);
postStream.commitPost(createdPost);
addedToStream = true;
} else {
Expand Down
4 changes: 3 additions & 1 deletion app/assets/javascripts/discourse/models/post_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,9 @@ Discourse.PostStream = Em.Object.extend({
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'));
postIdentityMap.set(post.get('id'), post);

Expand Down Expand Up @@ -822,7 +825,6 @@ Discourse.PostStream = Em.Object.extend({
@returns {Promise} a promise that will resolve to the posts in the order requested.
**/
loadIntoIdentityMap: function(postIds) {

// If we don't want any posts, return a promise that resolves right away
if (Em.isEmpty(postIds)) {
return Ember.RSVP.resolve();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<div class="auto-close-fields">
<div>
<i class="fa fa-clock-o"></i>
{{label}}
{{text-field value=autoCloseTime}}
{{i18n composer.auto_close_units}}
<label>
{{fa-icon clock-o}}
{{i18n composer.auto_close.label}}
{{text-field value=autoCloseTime}}
{{autoCloseUnits}}
</label>
</div>
<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>
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<form {{action "saveAutoClose" on="submit"}}>
<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 class="modal-footer">
<button class='btn btn-primary' type='submit' {{bind-attr disabled="auto_close_invalid"}}>{{i18n topic.auto_close_save}}</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
<section class='field'>
<div class="auto-close-fields">
<div>
<i class="fa fa-clock-o"></i>
{{i18n category.auto_close_label}}
{{text-field value=auto_close_hours}}
{{i18n category.auto_close_units}}
</div>
</div>
{{auto-close-form autoCloseTime=auto_close_hours
autoCloseBasedOnLastPost=auto_close_based_on_last_post
limited="true" }}
</section>

<section class='field'>
Expand Down
21 changes: 19 additions & 2 deletions app/assets/stylesheets/common/base/compose.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,27 @@ div.ac-wrap {
display: none;
}


.auto-close-fields {
div:not(:first-child) {
margin-top: 10px;
}
label {
font-size: 14px;
}
input {
text-align: center;
width: 150px;
}
.examples {
margin: 10px 0 0 0;
color: $primary;
font-style: italic;
}
}

.edit-auto-close-modal {
.btn.pull-right {
margin-right: 10px;
}
form {
margin: 0;
}
Expand All @@ -118,3 +127,11 @@ div.ac-wrap {
}
}
}

.edit-category-modal {
.auto-close-fields {
input[type=text] {
width: 50px;
}
}
}
1 change: 0 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,6 @@ def build_not_found_page(status=404, layout=false)
def render_post_json(post, add_raw=true)
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
post_serializer.add_raw = add_raw
post_serializer.topic_slug = post.topic.slug if post.topic.present?

counts = PostAction.counts_for([post], current_user)
if counts && counts = counts[post.id]
Expand Down
10 changes: 8 additions & 2 deletions app/controllers/categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,20 @@ def create

def update
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]

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
category_params[:email_in] = nil
end

category_params.delete(:position)

cat.update_attributes(category_params)
}
end
end

def set_notifications
Expand Down Expand Up @@ -125,6 +130,7 @@ def category_params
:email_in_allow_strangers,
:parent_category_id,
:auto_close_hours,
:auto_close_based_on_last_post,
:logo_url,
:background_url,
:allow_badges,
Expand Down
3 changes: 0 additions & 3 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def create_post(params)

else
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)
[true, MultiJson.dump(post_serializer)]
end
Expand Down Expand Up @@ -132,7 +131,6 @@ def update
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
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.topic_slug = post.topic.slug if post.topic.present?

result = {post: post_serializer.as_json}
if revisor.category_changed.present?
Expand Down Expand Up @@ -346,7 +344,6 @@ def create_params
:category,
:target_usernames,
:reply_to_post_number,
:auto_close_time,
:auto_track
]

Expand Down
12 changes: 10 additions & 2 deletions app/controllers/topics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,20 @@ def unmute
end

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)
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)

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
render_json_error(topic)
end
Expand Down
3 changes: 2 additions & 1 deletion app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ def self.post_template
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.skip_callbacks = true
t.auto_close_hours = nil
t.ignore_category_auto_close = true
t.set_auto_close(nil)
t.save!(validate: false)
update_column(:topic_id, t.id)
t.posts.create(raw: post_template, user: user)
Expand Down
Loading

0 comments on commit 5754e8d

Please sign in to comment.