diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 863836c1baf..414e117897e 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -779,11 +779,19 @@ export default Ember.Controller.extend({ @computed('model.categoryId', 'lastValidatedAt') categoryValidation(categoryId, lastValidatedAt) { - if( !this.siteSettings.allow_uncategorized_topics && !categoryId) { + if(!this.siteSettings.allow_uncategorized_topics && !categoryId) { return InputValidation.create({ failed: true, reason: I18n.t('composer.error.category_missing'), lastShownAt: lastValidatedAt }); } }, + @computed('model.category', 'model.tags', 'lastValidatedAt') + tagValidation(category, tags, lastValidatedAt) { + const tagsArray = tags || []; + if (this.site.get('can_tag_topics') && category && category.get('minimum_required_tags') > tagsArray.length) { + return InputValidation.create({ failed: true, reason: I18n.t('composer.error.tags_missing', {count: category.get('minimum_required_tags')}), lastShownAt: lastValidatedAt }); + } + }, + collapse() { this._saveDraft(); this.set('model.composeState', Composer.DRAFT); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 94a3e328001..8e4c6b60aed 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -105,6 +105,11 @@ const Composer = RestModel.extend({ return categoryId ? this.site.categories.findBy('id', categoryId) : null; }, + @computed('category') + minimumRequiredTags(category) { + return (category && category.get('minimum_required_tags') > 0) ? category.get('minimum_required_tags') : null; + }, + creatingTopic: Em.computed.equal('action', CREATE_TOPIC), creatingSharedDraft: Em.computed.equal('action', CREATE_SHARED_DRAFT), creatingPrivateMessage: Em.computed.equal('action', PRIVATE_MESSAGE), @@ -246,28 +251,41 @@ const Composer = RestModel.extend({ return options; }, - // whether to disable the post button - cantSubmitPost: function() { + @computed + isStaffUser() { + const currentUser = Discourse.User.current(); + return currentUser && currentUser.get('staff'); + }, + + @computed('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters', 'tags', 'topicFirstPost', 'minimumRequiredTags', 'isStaffUser') + cantSubmitPost(loading, canEditTitle, titleLength, targetUsernames, replyLength, categoryId, missingReplyCharacters, tags, topicFirstPost, minimumRequiredTags, isStaffUser) { // can't submit while loading - if (this.get('loading')) return true; + if (loading) return true; // title is required when // - creating a new topic/private message // - editing the 1st post - if (this.get('canEditTitle') && !this.get('titleLengthValid')) return true; + if (canEditTitle && !this.get('titleLengthValid')) return true; // reply is always required - if (this.get('missingReplyCharacters') > 0) return true; + if (missingReplyCharacters > 0) return true; + + if (this.site.get('can_tag_topics') && !isStaffUser && topicFirstPost && minimumRequiredTags) { + const tagsArray = tags || []; + if (tagsArray.length < minimumRequiredTags) { + return true; + } + } if (this.get("privateMessage")) { // need at least one user when sending a PM - return this.get('targetUsernames') && (this.get('targetUsernames').trim() + ',').indexOf(',') === 0; + return targetUsernames && (targetUsernames.trim() + ',').indexOf(',') === 0; } else { // has a category? (when needed) return this.get('requiredCategoryMissing'); } - }.property('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters'), + }, @computed('canCategorize', 'categoryId') requiredCategoryMissing(canCategorize, categoryId) { diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index cdda936d575..204c01ac729 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -64,7 +64,8 @@ {{/if}} {{#if canEditTags}} - {{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId}} + {{mini-tag-chooser tags=model.tags tabindex="4" categoryId=model.categoryId minimum=model.minimumRequiredTags}} + {{popup-input-tip validation=tagValidation}} {{/if}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d17851eaa25..97c5e7a8e5a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1303,6 +1303,7 @@ en: post_length: "Post must be at least {{min}} characters" try_like: 'Have you tried the button?' category_missing: "You must choose a category" + tags_missing: "You must choose at least {{count}} tags" save_edit: "Save Edit" reply_original: "Reply on Original Topic" diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index 6e63e6b803f..6394156fcb9 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -155,7 +155,7 @@ class TopicCreator def setup_tags(topic) if @opts[:tags].blank? - unless @guardian.is_staff? + unless @guardian.is_staff? || !guardian.can_tag?(topic) # Validate minimum required tags for a category category = find_category if category.present? && category.minimum_required_tags > 0 diff --git a/spec/components/topic_creator_spec.rb b/spec/components/topic_creator_spec.rb index d90e8e15f69..bb9586bfdb4 100644 --- a/spec/components/topic_creator_spec.rb +++ b/spec/components/topic_creator_spec.rb @@ -116,6 +116,13 @@ describe TopicCreator do expect(topic).to be_valid expect(topic.tags.length).to eq(2) end + + it "lets new user create a topic if they don't have sufficient trust level to tag topics" do + SiteSetting.min_trust_level_to_tag_topics = 1 + new_user = Fabricate(:newuser) + topic = TopicCreator.create(new_user, Guardian.new(new_user), valid_attrs.merge(category: "beta")) + expect(topic).to be_valid + end end end