From 5c39c8f24bd9495689b8b52f8d377398d944f426 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 20 Apr 2017 11:48:59 +0800 Subject: [PATCH] UX: Improve the way users set timers for `TopicStatusUpdate`. --- .../components/combo-box.js.es6 | 26 ++- .../auto-update-input-selector.js.es6 | 172 ++++++++++++++++++ .../components/auto-update-input.js.es6 | 117 ++++++++---- .../components/category-chooser.js.es6 | 4 +- .../components/composer-title.js.es6 | 6 +- .../components/date-picker-future.js.es6 | 3 +- .../discourse/components/date-picker.js.es6 | 7 +- .../components/topic-status-info.js.es6 | 24 +-- .../edit-topic-status-update.js.es6 | 70 +++---- .../javascripts/discourse/routes/topic.js.es6 | 2 +- .../components/auto-update-input.hbs | 56 ++++-- .../templates/components/date-picker.hbs | 2 +- .../components/edit-category-settings.hbs | 17 +- .../modal/edit-topic-status-update.hbs | 70 +------ .../javascripts/discourse/templates/topic.hbs | 8 +- .../base/edit-topic-status-update-modal.scss | 42 +++++ .../common/base/topic-close-modal.scss | 29 --- .../auto-update-input-selector.scss | 9 + .../stylesheets/common/printer-friendly.scss | 2 +- app/assets/stylesheets/desktop/topic.scss | 2 +- app/assets/stylesheets/mobile/topic.scss | 2 +- config/locales/client.en.yml | 22 ++- .../components/combo-box-test.js.es6 | 16 ++ 23 files changed, 472 insertions(+), 236 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/auto-update-input-selector.js.es6 create mode 100644 app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss delete mode 100644 app/assets/stylesheets/common/base/topic-close-modal.scss create mode 100644 app/assets/stylesheets/common/components/auto-update-input-selector.scss diff --git a/app/assets/javascripts/discourse-common/components/combo-box.js.es6 b/app/assets/javascripts/discourse-common/components/combo-box.js.es6 index 314cf47c457..e5828e8fc72 100644 --- a/app/assets/javascripts/discourse-common/components/combo-box.js.es6 +++ b/app/assets/javascripts/discourse-common/components/combo-box.js.es6 @@ -11,12 +11,14 @@ export default Ember.Component.extend(bufferedRender({ buildBuffer(buffer) { const nameProperty = this.get('nameProperty'); const none = this.get('none'); + let noneValue = null; // Add none option if required if (typeof none === "string") { buffer.push('"); } else if (typeof none === "object") { - buffer.push(""); + noneValue = Em.get(none, this.get('valueAttribute')); + buffer.push(``); } let selected = this.get('value'); @@ -47,7 +49,7 @@ export default Ember.Component.extend(bufferedRender({ }); } - if (!selectedFound) { + if (!selectedFound && !noneValue) { if (none) { this.set('value', null); } else { @@ -89,7 +91,8 @@ export default Ember.Component.extend(bufferedRender({ const $elem = this.$(); const caps = this.capabilities; - const minimumResultsForSearch = (caps && caps.isIOS) ? -1 : 5; + const minimumResultsForSearch = this.get('minimumResultsForSearch') || ((caps && caps.isIOS) ? -1 : 5); + if (!this.get("selectionTemplate") && this.get("selectionIcon")) { this.selectionTemplate = (item) => { let name = Em.get(item, 'text'); @@ -97,13 +100,22 @@ export default Ember.Component.extend(bufferedRender({ return `${name}`; }; } - $elem.select2({ - formatResult: this.comboTemplate, - formatSelection: this.selectionTemplate, + + const options = { minimumResultsForSearch, width: this.get('width') || 'resolve', allowClear: true - }); + }; + + if (this.comboTemplate) { + options.formatResult = this.comboTemplate.bind(this); + } + + if (this.selectionTemplate) { + options.formatSelection = this.selectionTemplate.bind(this); + } + + $elem.select2(options); const castInteger = this.get('castInteger'); $elem.on("change", e => { diff --git a/app/assets/javascripts/discourse/components/auto-update-input-selector.js.es6 b/app/assets/javascripts/discourse/components/auto-update-input-selector.js.es6 new file mode 100644 index 00000000000..353c6951cc9 --- /dev/null +++ b/app/assets/javascripts/discourse/components/auto-update-input-selector.js.es6 @@ -0,0 +1,172 @@ +import { default as computed, observes } from "ember-addons/ember-computed-decorators"; +import Combobox from 'discourse-common/components/combo-box'; +import { CLOSE_STATUS_TYPE } from 'discourse/controllers/edit-topic-status-update'; + +const LATER_TODAY = 'later_today'; +const TOMORROW = 'tomorrow'; +const LATER_THIS_WEEK = 'later_this_week'; +const THIS_WEEKEND = 'this_weekend'; +const NEXT_WEEK = 'next_week'; +export const PICK_DATE_AND_TIME = 'pick_date_and_time'; +export const SET_BASED_ON_LAST_POST = 'set_based_on_last_post'; + +export const FORMAT = 'YYYY-MM-DD HH:mm'; + +export default Combobox.extend({ + classNames: ['auto-update-input-selector'], + isCustom: Ember.computed.equal("value", PICK_DATE_AND_TIME), + + @computed() + content() { + const selections = []; + const now = moment(); + const canScheduleToday = (24 - now.hour()) > 6; + const day = now.day(); + + if (canScheduleToday) { + selections.push({ + id: LATER_TODAY, + name: I18n.t('topic.auto_update_input.later_today') + }); + } + + selections.push({ + id: TOMORROW, + name: I18n.t('topic.auto_update_input.tomorrow') + }); + + if (!canScheduleToday && day < 4) { + selections.push({ + id: LATER_THIS_WEEK, + name: I18n.t('topic.auto_update_input.later_this_week') + }); + } + + if (day < 5) { + selections.push({ + id: THIS_WEEKEND, + name: I18n.t('topic.auto_update_input.this_weekend') + }); + } + + + if (day !== 7) { + selections.push({ + id: NEXT_WEEK, + name: I18n.t('topic.auto_update_input.next_week') + }); + } + + selections.push({ + id: PICK_DATE_AND_TIME, + name: I18n.t('topic.auto_update_input.pick_date_and_time') + }); + + if (this.get('statusType') === CLOSE_STATUS_TYPE) { + selections.push({ + id: SET_BASED_ON_LAST_POST, + name: I18n.t('topic.auto_update_input.set_based_on_last_post') + }); + } + + return selections; + }, + + @observes('value') + _updateInput() { + if (this.get('isCustom')) return; + let input = null; + const { time } = this.get('updateAt'); + + if (time && !Ember.isEmpty(this.get('value'))) { + input = time.format(FORMAT); + } + + this.set('input', input); + }, + + @computed('value') + updateAt(value) { + return this._updateAt(value); + }, + + comboTemplate(state) { + return this._format(state); + }, + + selectionTemplate(state) { + return this._format(state); + }, + + _format(state) { + let { time, icon } = this._updateAt(state.id); + let icons; + + if (icon) { + icons = icon.split(',').map(i => { + return ``; + }).join(" "); + } + + if (time) { + if (state.id === LATER_TODAY) { + time = time.format('hh:mm a'); + } else { + time = time.format('ddd, hh:mm a'); + } + } + + let output = ""; + + if (!Ember.isEmpty(icons)) { + output += `${icons}`; + } + + output += `${state.text}`; + + if (time) { + output += `${time}`; + } + + return output; + }, + + _updateAt(selection) { + let time = moment(); + let icon; + const timeOfDay = this.get('statusType') !== CLOSE_STATUS_TYPE ? 8 : 18; + + switch(selection) { + case LATER_TODAY: + time = time.hour(18).minute(0); + icon = 'desktop'; + break; + case TOMORROW: + time = time.add(1, 'day').hour(timeOfDay).minute(0); + icon = 'sun-o'; + break; + case LATER_THIS_WEEK: + time = time.add(2, 'day').hour(timeOfDay).minute(0); + icon = 'briefcase'; + break; + case THIS_WEEKEND: + time = time.day(6).hour(timeOfDay).minute(0); + icon = 'bed'; + break; + case NEXT_WEEK: + time = time.add(1, 'week').day(1).hour(timeOfDay).minute(0); + icon = 'briefcase'; + break; + case PICK_DATE_AND_TIME: + time = null; + icon = 'calendar-plus-o'; + break; + case SET_BASED_ON_LAST_POST: + time = null; + icon = 'clock-o'; + break; + } + + return { time, icon }; + }, +}); diff --git a/app/assets/javascripts/discourse/components/auto-update-input.js.es6 b/app/assets/javascripts/discourse/components/auto-update-input.js.es6 index 1d441197782..747c0617933 100644 --- a/app/assets/javascripts/discourse/components/auto-update-input.js.es6 +++ b/app/assets/javascripts/discourse/components/auto-update-input.js.es6 @@ -1,47 +1,92 @@ import { default as computed, observes } from "ember-addons/ember-computed-decorators"; +import { + FORMAT, + PICK_DATE_AND_TIME, + SET_BASED_ON_LAST_POST +} from "discourse/components/auto-update-input-selector"; export default Ember.Component.extend({ - limited: false, + selection: null, + date: null, + time: null, + isCustom: Ember.computed.equal('selection', PICK_DATE_AND_TIME), + isBasedOnLastPost: Ember.computed.equal('selection', SET_BASED_ON_LAST_POST), - didInsertElement() { + init() { this._super(); - this._updateInputValid(); - }, - @computed("limited") - inputUnitsKey(limited) { - return limited ? "topic.auto_update_input.limited.units" : "topic.auto_update_input.all.units"; - }, + const input = this.get('input'); - @computed("limited") - inputExamplesKey(limited) { - return limited ? "topic.auto_update_input.limited.examples" : "topic.auto_update_input.all.examples"; - }, - - @observes("input", "limited") - _updateInputValid() { - this.set( - "inputValid", this._isInputValid(this.get("input"), this.get("limited")) - ); - }, - - _isInputValid(input, limited) { - const t = (input || "").toString().trim(); - - if (t.length === 0) { - return true; - // "empty" is always valid - } 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)?){0,1}$/i)) { - // timestamp must be in the future - return moment(t).isAfter(); + if (input) { + if (this.get('basedOnLastPost')) { + this.set('selection', SET_BASED_ON_LAST_POST); } else { - // either # of hours or absolute time - return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null; + this.set('selection', PICK_DATE_AND_TIME); + const datetime = moment(input); + this.set('date', datetime.toDate()); + this.set('time', datetime.format("HH:mm")); + this._updateInput(); } } - } + }, + + @observes("date", "time") + _updateInput() { + const date = moment(this.get('date')).format("YYYY-MM-DD"); + const time = (this.get('time') && ` ${this.get('time')}`) || ''; + this.set('input', moment(`${date}${time}`).format(FORMAT)); + }, + + @observes("isBasedOnLastPost") + _updateBasedOnLastPost() { + this.set('basedOnLastPost', this.get('isBasedOnLastPost')); + }, + + @computed("input", "isBasedOnLastPost") + duration(input, isBasedOnLastPost) { + const now = moment(); + + if (isBasedOnLastPost) { + return parseFloat(input); + } else { + return moment(input) - now; + } + }, + + @computed("input", "isBasedOnLastPost") + executeAt(input, isBasedOnLastPost) { + if (isBasedOnLastPost) { + return moment().add(input, 'hours').format(FORMAT); + } else { + return input; + } + }, + + @computed("statusType", "input", "isCustom", "date", "time", "willCloseImmediately") + showTopicStatusInfo(statusType, input, isCustom, date, time, willCloseImmediately) { + if (!statusType || willCloseImmediately) return false; + + if (isCustom) { + return date || time; + } else { + return input; + } + }, + + @computed('isBasedOnLastPost', 'input', 'lastPostedAt') + willCloseImmediately(isBasedOnLastPost, input, lastPostedAt) { + if (isBasedOnLastPost && input) { + let closeDate = moment(lastPostedAt); + closeDate = closeDate.add(input, 'hours'); + return closeDate < moment(); + } + }, + + @computed('isBasedOnLastPost', 'lastPostedAt') + willCloseI18n(isBasedOnLastPost, lastPostedAt) { + if (isBasedOnLastPost) { + const diff = Math.round((new Date() - new Date(lastPostedAt)) / (1000*60*60)); + return I18n.t('topic.auto_close_immediate', { count: diff }); + } + }, }); diff --git a/app/assets/javascripts/discourse/components/category-chooser.js.es6 b/app/assets/javascripts/discourse/components/category-chooser.js.es6 index b79e8ef1884..d0b8b45009a 100644 --- a/app/assets/javascripts/discourse/components/category-chooser.js.es6 +++ b/app/assets/javascripts/discourse/components/category-chooser.js.es6 @@ -1,11 +1,11 @@ -import ComboboxView from 'discourse-common/components/combo-box'; +import Combobox from 'discourse-common/components/combo-box'; import { categoryBadgeHTML } from 'discourse/helpers/category-link'; import computed from 'ember-addons/ember-computed-decorators'; import { observes, on } from 'ember-addons/ember-computed-decorators'; import PermissionType from 'discourse/models/permission-type'; import Category from 'discourse/models/category'; -export default ComboboxView.extend({ +export default Combobox.extend({ classNames: ['combobox category-combobox'], dataAttributes: ['id', 'description_text'], overrideWidths: true, diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6 index e9f1790ba58..0c173bdd0c3 100644 --- a/app/assets/javascripts/discourse/components/composer-title.js.es6 +++ b/app/assets/javascripts/discourse/components/composer-title.js.es6 @@ -110,9 +110,9 @@ export default Ember.Component.extend({ } }, - @computed('composer.title') - isAbsoluteUrl() { - return this.get('composer.titleLength') > 0 && /^(https?:)?\/\/[\w\.\-]+/i.test(this.get('composer.title')); + @computed('composer.title', 'composer.titleLength') + isAbsoluteUrl(title, titleLength) { + return titleLength > 0 && /^(https?:)?\/\/[\w\.\-]+/i.test(title); }, bodyIsDefault() { diff --git a/app/assets/javascripts/discourse/components/date-picker-future.js.es6 b/app/assets/javascripts/discourse/components/date-picker-future.js.es6 index fa6ed4037e2..249c1a57d6b 100644 --- a/app/assets/javascripts/discourse/components/date-picker-future.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker-future.js.es6 @@ -5,7 +5,8 @@ export default DatePicker.extend({ _opts() { return { - defaultDate: moment().add(1, "day").toDate(), + defaultDate: this.get('defaultDate') || moment().add(1, "day").toDate(), + setDefaultDate: !!this.get('defaultDate'), minDate: new Date(), }; } diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6 index de36036616c..d2667dd8637 100644 --- a/app/assets/javascripts/discourse/components/date-picker.js.es6 +++ b/app/assets/javascripts/discourse/components/date-picker.js.es6 @@ -1,6 +1,6 @@ /* global Pikaday:true */ import loadScript from "discourse/lib/load-script"; -import { on } from "ember-addons/ember-computed-decorators"; +import { default as computed, on } from "ember-addons/ember-computed-decorators"; export default Em.Component.extend({ classNames: ["date-picker-wrapper"], @@ -39,6 +39,11 @@ export default Em.Component.extend({ this._picker = null; }, + @computed() + placeholder() { + return I18n.t("dates.placeholder"); + }, + _opts() { return null; } diff --git a/app/assets/javascripts/discourse/components/topic-status-info.js.es6 b/app/assets/javascripts/discourse/components/topic-status-info.js.es6 index a7b5d784b04..8cb29a0b21c 100644 --- a/app/assets/javascripts/discourse/components/topic-status-info.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-status-info.js.es6 @@ -2,21 +2,21 @@ import { bufferedRender } from 'discourse-common/lib/buffered-render'; import Category from 'discourse/models/category'; export default Ember.Component.extend(bufferedRender({ - elementId: 'topic-status-info', + classNames: ['topic-status-info'], delayedRerender: null, rerenderTriggers: [ - 'topic.topic_status_update', - 'topic.topic_status_update.execute_at', - 'topic.topic_status_update.based_on_last_post', - 'topic.topic_status_update.duration', - 'topic.topic_status_update.category_id', + 'statusType', + 'executeAt', + 'basedOnLastPost', + 'duration', + 'categoryId', ], buildBuffer(buffer) { - if (!this.get('topic.topic_status_update.execute_at')) return; + if (!this.get('executeAt')) return; - let statusUpdateAt = moment(this.get('topic.topic_status_update.execute_at')); + let statusUpdateAt = moment(this.get('executeAt')); if (statusUpdateAt < new Date()) return; let duration = moment.duration(statusUpdateAt - moment()); @@ -33,7 +33,7 @@ export default Ember.Component.extend(bufferedRender({ rerenderDelay = 60000; } - let autoCloseHours = this.get("topic.topic_status_update.duration") || 0; + let autoCloseHours = this.get("duration") || 0; buffer.push('

'); @@ -42,7 +42,7 @@ export default Ember.Component.extend(bufferedRender({ duration: moment.duration(autoCloseHours, "hours").humanize(), }; - const categoryId = this.get('topic.topic_status_update.category_id'); + const categoryId = this.get('categoryId'); if (categoryId) { const category = Category.findById(categoryId); @@ -67,9 +67,9 @@ export default Ember.Component.extend(bufferedRender({ }, _noticeKey() { - const statusType = this.get('topic.topic_status_update.status_type'); + const statusType = this.get('statusType'); - if (this.get("topic.topic_status_update.based_on_last_post")) { + if (this.get("basedOnLastPost")) { return `topic.status_update_notice.auto_${statusType}_based_on_last_post`; } else { return `topic.status_update_notice.auto_${statusType}`; diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 index 3dbeddeca51..bde5384560e 100644 --- a/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 +++ b/app/assets/javascripts/discourse/controllers/edit-topic-status-update.js.es6 @@ -3,16 +3,11 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import TopicStatusUpdate from 'discourse/models/topic-status-update'; import { popupAjaxError } from 'discourse/lib/ajax-error'; -const CLOSE_STATUS_TYPE = 'close'; +export const CLOSE_STATUS_TYPE = 'close'; const OPEN_STATUS_TYPE = 'open'; const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category'; export default Ember.Controller.extend(ModalFunctionality, { - closeStatusType: CLOSE_STATUS_TYPE, - openStatusType: OPEN_STATUS_TYPE, - publishToCategoryStatusType: PUBLISH_TO_CATEGORY_STATUS_TYPE, - updateTimeValid: null, - updateTimeInvalid: Em.computed.not('updateTimeValid'), loading: false, updateTime: null, topicStatusUpdate: Ember.computed.alias("model.topic_status_update"), @@ -21,42 +16,18 @@ export default Ember.Controller.extend(ModalFunctionality, { autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE), publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE), - @computed('autoClose', 'updateTime') - disableAutoClose(autoClose, updateTime) { - return updateTime && !autoClose; + @computed("model.closed") + statusUpdates(closed) { + return [ + { id: CLOSE_STATUS_TYPE, name: I18n.t(closed ? 'topic.temp_open.title' : 'topic.auto_close.title'), }, + { id: OPEN_STATUS_TYPE, name: I18n.t(closed ? 'topic.auto_reopen.title' : 'topic.temp_close.title') }, + { id: PUBLISH_TO_CATEGORY_STATUS_TYPE, name: I18n.t('topic.publish_to_category.title') } + ]; }, - @computed('autoOpen', 'updateTime') - disableAutoOpen(autoOpen, updateTime) { - return updateTime && !autoOpen; - }, - - @computed('publishToCatgory', 'updateTime') - disablePublishToCategory(publishToCatgory, updateTime) { - return updateTime && !publishToCatgory; - }, - - @computed('topicStatusUpdate.based_on_last_post', 'updateTime', 'model.last_posted_at') - willCloseImmediately(basedOnLastPost, updateTime, lastPostedAt) { - if (!basedOnLastPost) { - return false; - } - const closeDate = new Date(lastPostedAt); - closeDate.setHours(closeDate.getHours() + updateTime); - return closeDate < new Date(); - }, - - @computed('topicStatusUpdate.based_on_last_post', 'model.last_posted_at') - willCloseI18n(basedOnLastPost, lastPostedAt) { - if (basedOnLastPost) { - const diff = Math.round((new Date() - new Date(lastPostedAt)) / (1000*60*60)); - return I18n.t('topic.auto_close_immediate', { count: diff }); - } - }, - - @computed('updateTime', 'updateTimeInvalid', 'loading') - saveDisabled(updateTime, updateTimeInvalid, loading) { - return Ember.isEmpty(updateTime) || updateTimeInvalid || loading; + @computed('updateTime', 'loading') + saveDisabled(updateTime, loading) { + return Ember.isEmpty(updateTime) || loading; }, @computed("model.visible") @@ -66,29 +37,31 @@ export default Ember.Controller.extend(ModalFunctionality, { @observes("topicStatusUpdate.execute_at", "topicStatusUpdate.duration") _setUpdateTime() { + if (!this.get('topicStatusUpdate.execute_at')) return; + let time = null; if (this.get("topicStatusUpdate.based_on_last_post")) { time = this.get("topicStatusUpdate.duration"); } else if (this.get("topicStatusUpdate.execute_at")) { - const closeTime = new Date(this.get("topicStatusUpdate.execute_at")); + const closeTime = moment(this.get('topicStatusUpdate.execute_at')); - if (closeTime > new Date()) { - time = moment(closeTime).format("YYYY-MM-DD HH:mm"); + if (closeTime > moment()) { + time = closeTime.format("YYYY-MM-DD HH:mm"); } } this.set("updateTime", time); }, - _setStatusUpdate(time, status_type) { + _setStatusUpdate(time, statusType) { this.set('loading', true); TopicStatusUpdate.updateStatus( this.get('model.id'), time, this.get('topicStatusUpdate.based_on_last_post'), - status_type, + statusType, this.get('categoryId') ).then(result => { if (time) { @@ -102,8 +75,11 @@ export default Ember.Controller.extend(ModalFunctionality, { this.set('model.closed', result.closed); } else { - this.set('topicStatusUpdate', Ember.Object.create({})); - this.set('selection', null); + this.setProperties({ + topicStatusUpdate: Ember.Object.create({}), + selection: null, + updateTime: null + }); } }).catch(error => { popupAjaxError(error); diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index bcc266dc863..616ca7e0002 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -54,7 +54,7 @@ const TopicRoute = Discourse.Route.extend({ const model = this.modelFor('topic'); model.set('topic_status_update', Ember.Object.create(model.get('topic_status_update'))); showModal('edit-topic-status-update', { model }); - this.controllerFor('modal').set('modalClass', 'topic-close-modal'); + this.controllerFor('modal').set('modalClass', 'edit-topic-status-update-modal'); }, showChangeTimestamp() { diff --git a/app/assets/javascripts/discourse/templates/components/auto-update-input.hbs b/app/assets/javascripts/discourse/templates/components/auto-update-input.hbs index 455f17eea36..65267c23c59 100644 --- a/app/assets/javascripts/discourse/templates/components/auto-update-input.hbs +++ b/app/assets/javascripts/discourse/templates/components/auto-update-input.hbs @@ -1,24 +1,52 @@
- + - {{#if inputExamplesKey}} -
- {{i18n inputExamplesKey}} -
- {{/if}} + {{auto-update-input-selector + valueAttribute="id" + minimumResultsForSearch=-1 + statusType=statusType + value=selection + input=input + width="50%" + none="topic.auto_update_input.none"}}
- {{#unless hideBasedOnLastPost}} + {{#if isCustom}} +
+ {{fa-icon "calendar"}} {{date-picker-future value=date defaultDate=date}} +
+ +
+ {{fa-icon "clock-o"}} + {{input type="time" value=time}} +
+ {{/if}} + + {{#if isBasedOnLastPost}}
- {{/unless}} + + {{#if willCloseImmediately}} +
+ {{fa-icon "warning"}} + {{willCloseI18n}} +
+ {{/if}} + {{/if}} + + {{#if showTopicStatusInfo}} +
+ {{topic-status-info + statusType=statusType + executeAt=executeAt + basedOnLastPost=basedOnLastPost + duration=duration + categoryId=categoryId}} +
+ {{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/date-picker.hbs b/app/assets/javascripts/discourse/templates/components/date-picker.hbs index a2c89401aba..d49379d954b 100644 --- a/app/assets/javascripts/discourse/templates/components/date-picker.hbs +++ b/app/assets/javascripts/discourse/templates/components/date-picker.hbs @@ -1 +1 @@ - +{{input type="text" class="date-picker" placeholder=placeholder}} diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs index e1fec090805..ddfb1ce11be 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-settings.hbs @@ -1,10 +1,15 @@
- {{auto-update-input - inputLabelKey='topic.auto_close.label' - input=category.auto_close_hours - basedOnLastPost=category.auto_close_based_on_last_post - inputExamplesKey='' - limited=true}} +
+ + + +
diff --git a/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs index 8fca98f4132..c1039fd9b42 100644 --- a/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs +++ b/app/assets/javascripts/discourse/templates/modal/edit-topic-status-update.hbs @@ -1,59 +1,14 @@
{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}} -
- {{radio-button - disabled=disableAutoClose - name="auto-close" - id="auto-close" - value=closeStatusType - selection=selection}} - - - - {{radio-button - disabled=disableAutoOpen - name="auto-reopen" - id="auto-reopen" - value=openStatusType - selection=selection}} - - - - {{radio-button - disabled=disablePublishToCategory - name="publish-to-category" - id="publish-to-category" - value=publishToCategoryStatusType - selection=selection}} - - +
+ {{combo-box content=statusUpdates value=selection width="50%"}}
{{#if autoOpen}} {{auto-update-input - inputLabelKey='topic.topic_status_update.time' input=updateTime - inputValid=updateTimeValid - hideBasedOnLastPost=true + statusType=selection basedOnLastPost=false}} {{else if publishToCategory}}
@@ -62,25 +17,16 @@
{{auto-update-input - inputLabelKey='topic.topic_status_update.time' input=updateTime - inputValid=updateTimeValid - hideBasedOnLastPost=true + statusType=selection + categoryId=categoryId basedOnLastPost=false}} {{else if autoClose}} {{auto-update-input - inputLabelKey='topic.topic_status_update.time' input=updateTime - inputValid=updateTimeValid - limited=topicStatusUpdate.based_on_last_post - basedOnLastPost=topicStatusUpdate.based_on_last_post}} - - {{#if willCloseImmediately}} -
- {{fa-icon "warning"}} - {{willCloseI18n}} -
- {{/if}} + statusType=selection + basedOnLastPost=topicStatusUpdate.based_on_last_post + lastPostedAt=model.last_posted_at}} {{/if}}
{{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index c22ed994bb2..4c167b8e39e 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -174,7 +174,13 @@ {{#conditional-loading-spinner condition=model.postStream.loadingFilter}} {{#if loadedAllPosts}} - {{topic-status-info topic=model}} + {{topic-status-info + statusType=model.topic_status_update.status_type + executeAt=model.topic_status_update.execute_at + basedOnLastPost=model.topic_status_update.based_on_last_post + duration=model.topic_status_update.duration + categoryId=model.topic_status_update.category_id}} + {{#if session.showSignupCta}} {{! replace "Log In to Reply" with the infobox }} {{signup-cta}} diff --git a/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss new file mode 100644 index 00000000000..65b06ed8f4d --- /dev/null +++ b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss @@ -0,0 +1,42 @@ +.edit-topic-status-update-modal { + .modal-body { + max-height: none; + } + + input.date-picker, input[type="time"] { + width: 150px; + text-align: left; + } + + label { + display: inline-block; + } + + .btn.pull-right { + margin-right: 10px; + } + + .auto-update-input { + input { + margin: 0; + } + + .alert-info { + margin: 0 -15px -15px -15px; + } + + .pika-single { + position: relative !important; + } + + .topic-status-info { + border: none; + padding: 0; + + h3 { + font-weight: normal; + font-size: 15px; + } + } + } +} diff --git a/app/assets/stylesheets/common/base/topic-close-modal.scss b/app/assets/stylesheets/common/base/topic-close-modal.scss deleted file mode 100644 index 0a638b6f819..00000000000 --- a/app/assets/stylesheets/common/base/topic-close-modal.scss +++ /dev/null @@ -1,29 +0,0 @@ -.topic-close-modal { - label { - display: inline-block; - } - - .radios { - padding-bottom: 20px; - display: inline-block; - - input[type='radio'] { - vertical-align: middle; - margin: 0px; - } - - label { - padding: 0 10px 0px 5px; - } - } - - .btn.pull-right { - margin-right: 10px; - } - - .auto-update-input { - input { - margin: 0; - } - } -} diff --git a/app/assets/stylesheets/common/components/auto-update-input-selector.scss b/app/assets/stylesheets/common/components/auto-update-input-selector.scss new file mode 100644 index 00000000000..65207f3c1cd --- /dev/null +++ b/app/assets/stylesheets/common/components/auto-update-input-selector.scss @@ -0,0 +1,9 @@ +.auto-update-input-selector-datetime { + float: right; + color: lighten($primary, 40%); + font-size: 13px; +} + +.auto-update-input-selector-icons { + margin-right: 10px; +} diff --git a/app/assets/stylesheets/common/printer-friendly.scss b/app/assets/stylesheets/common/printer-friendly.scss index 6d62eb40d8d..d640737b47e 100644 --- a/app/assets/stylesheets/common/printer-friendly.scss +++ b/app/assets/stylesheets/common/printer-friendly.scss @@ -16,7 +16,7 @@ .show-topic-admin, #topic-progress, .quote-controls, - #topic-status-info, + .topic-status-info, div.lazyYT, .post-info.edits, .post-action, diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss index 59d297ac479..bd4b0c9f5f0 100644 --- a/app/assets/stylesheets/desktop/topic.scss +++ b/app/assets/stylesheets/desktop/topic.scss @@ -79,7 +79,7 @@ } } -#topic-status-info { +.topic-status-info { border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%); padding-top: 10px; height: 20px; diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss index 4365009a6a3..7e0fc0cac6a 100644 --- a/app/assets/stylesheets/mobile/topic.scss +++ b/app/assets/stylesheets/mobile/topic.scss @@ -43,7 +43,7 @@ clear: both; } -#topic-status-info { +.topic-status-info { margin-left: 10px; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 61e65b20af4..8c9540977dd 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -127,6 +127,7 @@ en: other: "%{count} years later" previous_month: 'Previous Month' next_month: 'Next Month' + placeholder: Pick a date share: topic: 'share a link to this topic' post: 'post #%{postNumber}' @@ -1484,16 +1485,19 @@ en: topic_status_update: title: "Set Topic Timer" save: "Set Timer" - time: "Time:" + num_of_hours: "Number of hours:" remove: "Remove Timer" publish_to: "Publish To:" + when: "When:" auto_update_input: - limited: - units: "(# of hours)" - examples: 'Enter number of hours (24).' - all: - units: "" - examples: 'Enter number of hours (24), absolute time (17:30) or timestamp (2013-11-22 14:00).' + none: "" + later_today: "Later today" + tomorrow: "Tomorrow" + later_this_week: "Later this week" + this_weekend: "This weekend" + next_week: "Next week" + pick_date_and_time: "Pick date and time" + set_based_on_last_post: "Close based on last post" publish_to_category: title: "Schedule Publishing" temp_open: @@ -1504,7 +1508,7 @@ en: title: "Close Temporarily" auto_close: title: "Auto-Close Topic" - label: "Auto-close topic time:" + label: "Auto-close topic hours:" error: "Please enter a valid value." based_on_last_post: "Don't close until the last post in the topic is at least this old." @@ -2010,8 +2014,6 @@ en: security: "Security" special_warning: "Warning: This category is a pre-seeded category and the security settings cannot be edited. If you do not wish to use this category, delete it instead of repurposing it." images: "Images" - auto_close_label: "Auto-close topics after:" - auto_close_units: "hours" email_in: "Custom incoming email address:" email_in_allow_strangers: "Accept emails from anonymous users with no accounts" email_in_disabled: "Posting new topics via email is disabled in the Site Settings. To enable posting new topics via email, " diff --git a/test/javascripts/components/combo-box-test.js.es6 b/test/javascripts/components/combo-box-test.js.es6 index 174905548eb..39232362ffb 100644 --- a/test/javascripts/components/combo-box-test.js.es6 +++ b/test/javascripts/components/combo-box-test.js.es6 @@ -56,3 +56,19 @@ componentTest('with none', { assert.equal(this.$("select option:eq(2)").text(), 'trout'); } }); + +componentTest('with Object none', { + template: '{{combo-box content=items none=none value=value selected="something"}}', + setup() { + this.set('none', { id: 'something', name: 'none' }); + this.set('items', ['evil', 'trout', 'hat']); + }, + + test(assert) { + assert.equal(this.get('value'), 'something'); + assert.equal(this.$("select option:eq(0)").text(), 'none'); + assert.equal(this.$("select option:eq(0)").val(), 'something'); + assert.equal(this.$("select option:eq(1)").text(), 'evil'); + assert.equal(this.$("select option:eq(2)").text(), 'trout'); + } +});