diff --git a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 index 5c276aa9077..48d49e6109c 100644 --- a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 +++ b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 @@ -1,5 +1,6 @@ import StringBuffer from 'discourse/mixins/string-buffer'; import { iconHTML } from 'discourse/helpers/fa-icon'; +import { observes } from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend(StringBuffer, { classNameBindings: [':popup-tip', 'good', 'bad', 'shownAt::hide'], @@ -12,27 +13,23 @@ export default Ember.Component.extend(StringBuffer, { this.set('shownAt', false); }, - good: function() { - return !this.get('validation.failed'); - }.property('validation'), + bad: Ember.computed.alias("validation.failed"), + good: Ember.computed.not("bad"), - bad: function() { - return this.get('validation.failed'); - }.property('validation'), - - bounce: function() { - if( this.get('shownAt') ) { + @observes("shownAt") + bounce(shownAt) { + if (shownAt) { var $elem = this.$(); - if( !this.animateAttribute ) { + if (!this.animateAttribute) { this.animateAttribute = $elem.css('left') === 'auto' ? 'right' : 'left'; } - if( this.animateAttribute === 'left' ) { + if (this.animateAttribute === 'left') { this.bounceLeft($elem); } else { this.bounceRight($elem); } } - }.observes('shownAt'), + }, renderString(buffer) { const reason = this.get('validation.reason'); @@ -43,13 +40,13 @@ export default Ember.Component.extend(StringBuffer, { }, bounceLeft($elem) { - for( var i = 0; i < 5; i++ ) { + for (var i = 0; i < 5; i++) { $elem.animate({ left: '+=' + this.bouncePixels }, this.bounceDelay).animate({ left: '-=' + this.bouncePixels }, this.bounceDelay); } }, bounceRight($elem) { - for( var i = 0; i < 5; i++ ) { + for (var i = 0; i < 5; i++) { $elem.animate({ right: '-=' + this.bouncePixels }, this.bounceDelay).animate({ right: '+=' + this.bouncePixels }, this.bounceDelay); } } diff --git a/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 b/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 index 1cadd78ca49..4b189d168d3 100644 --- a/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/feature-topic.js.es6 @@ -11,31 +11,38 @@ export default Ember.Controller.extend(ModalFunctionality, { bannerCount: 0, reset() { - this.set("model.pinnedInCategoryUntil", null); - this.set("model.pinnedGloballyUntil", null); + this.setProperties({ + "model.pinnedInCategoryUntil": null, + "model.pinnedGloballyUntil": null, + pinInCategoryTipShownAt: false, + pinGloballyTipShownAt: false, + }); }, - categoryLink: function() { - return categoryLinkHTML(this.get("model.category"), { allowUncategorized: true }); - }.property("model.category"), + @computed("model.category") + categoryLink(category) { + return categoryLinkHTML(category, { allowUncategorized: true }); + }, - unPinMessage: function() { + @computed("categoryLink", "model.pinned_globally", "model.pinned_until") + unPinMessage(categoryLink, pinnedGlobally, pinnedUntil) { let name = "topic.feature_topic.unpin"; - if (this.get("model.pinned_globally")) name += "_globally"; - if (moment(this.get("model.pinned_until")) > moment()) name += "_until"; - const until = moment(this.get("model.pinned_until")).format("LL"); + if (pinnedGlobally) name += "_globally"; + if (moment(pinnedUntil) > moment()) name += "_until"; + const until = moment(pinnedUntil).format("LL"); - return I18n.t(name, { categoryLink: this.get("categoryLink"), until: until }); - }.property("categoryLink", "model.{pinned_globally,pinned_until}"), + return I18n.t(name, { categoryLink, until }); + }, @computed("categoryLink") pinMessage(categoryLink) { return I18n.t("topic.feature_topic.pin", { categoryLink }); }, - alreadyPinnedMessage: function() { - return I18n.t("topic.feature_topic.already_pinned", { categoryLink: this.get("categoryLink"), count: this.get("pinnedInCategoryCount") }); - }.property("categoryLink", "pinnedInCategoryCount"), + @computed("categoryLink", "pinnedInCategoryCount") + alreadyPinnedMessage(categoryLink, count) { + return I18n.t("topic.feature_topic.already_pinned", { categoryLink, count }); + }, @computed("parsedPinnedInCategoryUntil") pinDisabled(parsedPinnedInCategoryUntil) { @@ -47,13 +54,29 @@ export default Ember.Controller.extend(ModalFunctionality, { return !this._isDateValid(parsedPinnedGloballyUntil); }, - parsedPinnedInCategoryUntil: function() { - return this._parseDate(this.get("model.pinnedInCategoryUntil")); - }.property("model.pinnedInCategoryUntil"), + @computed("model.pinnedInCategoryUntil") + parsedPinnedInCategoryUntil(pinnedInCategoryUntil) { + return this._parseDate(pinnedInCategoryUntil); + }, - parsedPinnedGloballyUntil: function() { - return this._parseDate(this.get("model.pinnedGloballyUntil")); - }.property("model.pinnedGloballyUntil"), + @computed("model.pinnedGloballyUntil") + parsedPinnedGloballyUntil(pinnedGloballyUntil) { + return this._parseDate(pinnedGloballyUntil); + }, + + @computed("pinDisabled") + pinInCategoryValidation(pinDisabled) { + if (pinDisabled) { + return Discourse.InputValidation.create({ failed: true, reason: I18n.t("topic.feature_topic.pin_validation") }); + } + }, + + @computed("pinGloballyDisabled") + pinGloballyValidation(pinGloballyDisabled) { + if (pinGloballyDisabled) { + return Discourse.InputValidation.create({ failed: true, reason: I18n.t("topic.feature_topic.pin_validation") }); + } + }, _parseDate(date) { return moment(date, ["YYYY-MM-DD", "YYYY-MM-DD HH:mm"]); @@ -90,7 +113,7 @@ export default Ember.Controller.extend(ModalFunctionality, { } else { this.send("hideModal"); bootbox.confirm( - I18n.t("topic.feature_topic.confirm_" + name, { count: count }), + I18n.t("topic.feature_topic.confirm_" + name, { count }), I18n.t("no_value"), I18n.t("yes_value"), confirmed => confirmed ? this._forwardAction(action) : this.send("reopenModal") @@ -99,8 +122,23 @@ export default Ember.Controller.extend(ModalFunctionality, { }, actions: { - pin() { this._forwardAction("togglePinned"); }, - pinGlobally() { this._confirmBeforePinning(this.get("pinnedGloballyCount"), "pin_globally", "pinGlobally"); }, + pin() { + if (this.get("pinDisabled")) { + this.set("pinInCategoryTipShownAt", Date.now()); + } else { + this._forwardAction("togglePinned"); + } + }, + + pinGlobally() { + if (this.get("pinGloballyDisabled")) { + this.set("pinGloballyTipShownAt", Date.now()); + } else { + this._confirmBeforePinning(this.get("pinnedGloballyCount"), "pin_globally", "pinGlobally"); + } + }, + + unpin() { this._forwardAction("togglePinned"); }, makeBanner() { this._forwardAction("makeBanner"); }, removeBanner() { this._forwardAction("removeBanner"); }, diff --git a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs index d6360b86de8..7aee95425e3 100644 --- a/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs +++ b/app/assets/javascripts/discourse/templates/modal/feature-topic.hbs @@ -32,13 +32,14 @@

{{i18n "topic.feature_topic.pin_note"}}

-

+

{{{pinMessage}}} {{fa-icon "clock-o"}} {{date-picker value=model.pinnedInCategoryUntil}} + {{popup-input-tip validation=pinInCategoryValidation shownAt=pinInCategoryTipShownAt}}

- {{d-button action="pin" icon="thumb-tack" label="topic.feature.pin" class="btn-primary" disabled=pinDisabled}} + {{d-button action="pin" icon="thumb-tack" label="topic.feature.pin" class="btn-primary"}}

@@ -53,13 +54,14 @@

{{i18n "topic.feature_topic.global_pin_note"}}

-

+

{{i18n "topic.feature_topic.pin_globally"}} {{fa-icon "clock-o"}} {{date-picker value=model.pinnedGloballyUntil}} + {{popup-input-tip validation=pinGloballyValidation shownAt=pinGloballyTipShownAt}}

- {{d-button action="pinGlobally" icon="thumb-tack" label="topic.feature.pin_globally" class="btn-primary" disabled=pinGloballyDisabled}} + {{d-button action="pinGlobally" icon="thumb-tack" label="topic.feature.pin_globally" class="btn-primary"}}

diff --git a/app/assets/stylesheets/common/base/topic-admin-menu.scss b/app/assets/stylesheets/common/base/topic-admin-menu.scss index 2589ad8d8a8..a0db149139c 100644 --- a/app/assets/stylesheets/common/base/topic-admin-menu.scss +++ b/app/assets/stylesheets/common/base/topic-admin-menu.scss @@ -57,6 +57,9 @@ margin: 10px 0 0; } } + .with-validation { + position: relative; + } } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 94fb4e0a912..da3b03389ed 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1157,6 +1157,7 @@ en: unpin: "Remove this topic from the top of the {{categoryLink}} category." unpin_until: "Remove this topic from the top of the {{categoryLink}} category or wait until %{until}." pin_note: "Users can unpin the topic individually for themselves." + pin_validation: "A date is required to pin this topic." already_pinned: zero: "There are no topics pinned in {{categoryLink}}." one: "Topics currently pinned in {{categoryLink}}: 1."