UX: Improve the way users set timers for `TopicStatusUpdate`.

This commit is contained in:
Guo Xiang Tan 2017-04-20 11:48:59 +08:00
parent 0f2e2ea175
commit 5c39c8f24b
23 changed files with 472 additions and 236 deletions

View File

@ -11,12 +11,14 @@ export default Ember.Component.extend(bufferedRender({
buildBuffer(buffer) { buildBuffer(buffer) {
const nameProperty = this.get('nameProperty'); const nameProperty = this.get('nameProperty');
const none = this.get('none'); const none = this.get('none');
let noneValue = null;
// Add none option if required // Add none option if required
if (typeof none === "string") { if (typeof none === "string") {
buffer.push('<option value="">' + I18n.t(none) + "</option>"); buffer.push('<option value="">' + I18n.t(none) + "</option>");
} else if (typeof none === "object") { } else if (typeof none === "object") {
buffer.push("<option value=\"\">" + Em.get(none, nameProperty) + "</option>"); noneValue = Em.get(none, this.get('valueAttribute'));
buffer.push(`<option value="${noneValue}">${Em.get(none, nameProperty)}</option>`);
} }
let selected = this.get('value'); let selected = this.get('value');
@ -47,7 +49,7 @@ export default Ember.Component.extend(bufferedRender({
}); });
} }
if (!selectedFound) { if (!selectedFound && !noneValue) {
if (none) { if (none) {
this.set('value', null); this.set('value', null);
} else { } else {
@ -89,7 +91,8 @@ export default Ember.Component.extend(bufferedRender({
const $elem = this.$(); const $elem = this.$();
const caps = this.capabilities; 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")) { if (!this.get("selectionTemplate") && this.get("selectionIcon")) {
this.selectionTemplate = (item) => { this.selectionTemplate = (item) => {
let name = Em.get(item, 'text'); let name = Em.get(item, 'text');
@ -97,13 +100,22 @@ export default Ember.Component.extend(bufferedRender({
return `<i class='fa fa-${this.get("selectionIcon")}'></i>${name}`; return `<i class='fa fa-${this.get("selectionIcon")}'></i>${name}`;
}; };
} }
$elem.select2({
formatResult: this.comboTemplate, const options = {
formatSelection: this.selectionTemplate,
minimumResultsForSearch, minimumResultsForSearch,
width: this.get('width') || 'resolve', width: this.get('width') || 'resolve',
allowClear: true 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'); const castInteger = this.get('castInteger');
$elem.on("change", e => { $elem.on("change", e => {

View File

@ -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 `<i class='fa fa-${i}'/>`;
}).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 += `<span class='auto-update-input-selector-icons'>${icons}</span>`;
}
output += `<span>${state.text}</span>`;
if (time) {
output += `<span class='auto-update-input-selector-datetime'>${time}</span>`;
}
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 };
},
});

View File

@ -1,47 +1,92 @@
import { default as computed, observes } from "ember-addons/ember-computed-decorators"; 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({ 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._super();
this._updateInputValid();
},
@computed("limited") const input = this.get('input');
inputUnitsKey(limited) {
return limited ? "topic.auto_update_input.limited.units" : "topic.auto_update_input.all.units";
},
@computed("limited") if (input) {
inputExamplesKey(limited) { if (this.get('basedOnLastPost')) {
return limited ? "topic.auto_update_input.limited.examples" : "topic.auto_update_input.all.examples"; this.set('selection', SET_BASED_ON_LAST_POST);
},
@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();
} else { } else {
// either # of hours or absolute time this.set('selection', PICK_DATE_AND_TIME);
return (t.match(/^(\d+\.)?\d+$/) || t.match(/^\d{1,2}:\d{2}(\s?[AP]M)?$/i)) !== null; 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 });
}
},
}); });

View File

@ -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 { categoryBadgeHTML } from 'discourse/helpers/category-link';
import computed from 'ember-addons/ember-computed-decorators'; import computed from 'ember-addons/ember-computed-decorators';
import { observes, on } from 'ember-addons/ember-computed-decorators'; import { observes, on } from 'ember-addons/ember-computed-decorators';
import PermissionType from 'discourse/models/permission-type'; import PermissionType from 'discourse/models/permission-type';
import Category from 'discourse/models/category'; import Category from 'discourse/models/category';
export default ComboboxView.extend({ export default Combobox.extend({
classNames: ['combobox category-combobox'], classNames: ['combobox category-combobox'],
dataAttributes: ['id', 'description_text'], dataAttributes: ['id', 'description_text'],
overrideWidths: true, overrideWidths: true,

View File

@ -110,9 +110,9 @@ export default Ember.Component.extend({
} }
}, },
@computed('composer.title') @computed('composer.title', 'composer.titleLength')
isAbsoluteUrl() { isAbsoluteUrl(title, titleLength) {
return this.get('composer.titleLength') > 0 && /^(https?:)?\/\/[\w\.\-]+/i.test(this.get('composer.title')); return titleLength > 0 && /^(https?:)?\/\/[\w\.\-]+/i.test(title);
}, },
bodyIsDefault() { bodyIsDefault() {

View File

@ -5,7 +5,8 @@ export default DatePicker.extend({
_opts() { _opts() {
return { return {
defaultDate: moment().add(1, "day").toDate(), defaultDate: this.get('defaultDate') || moment().add(1, "day").toDate(),
setDefaultDate: !!this.get('defaultDate'),
minDate: new Date(), minDate: new Date(),
}; };
} }

View File

@ -1,6 +1,6 @@
/* global Pikaday:true */ /* global Pikaday:true */
import loadScript from "discourse/lib/load-script"; 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({ export default Em.Component.extend({
classNames: ["date-picker-wrapper"], classNames: ["date-picker-wrapper"],
@ -39,6 +39,11 @@ export default Em.Component.extend({
this._picker = null; this._picker = null;
}, },
@computed()
placeholder() {
return I18n.t("dates.placeholder");
},
_opts() { _opts() {
return null; return null;
} }

View File

@ -2,21 +2,21 @@ import { bufferedRender } from 'discourse-common/lib/buffered-render';
import Category from 'discourse/models/category'; import Category from 'discourse/models/category';
export default Ember.Component.extend(bufferedRender({ export default Ember.Component.extend(bufferedRender({
elementId: 'topic-status-info', classNames: ['topic-status-info'],
delayedRerender: null, delayedRerender: null,
rerenderTriggers: [ rerenderTriggers: [
'topic.topic_status_update', 'statusType',
'topic.topic_status_update.execute_at', 'executeAt',
'topic.topic_status_update.based_on_last_post', 'basedOnLastPost',
'topic.topic_status_update.duration', 'duration',
'topic.topic_status_update.category_id', 'categoryId',
], ],
buildBuffer(buffer) { 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; if (statusUpdateAt < new Date()) return;
let duration = moment.duration(statusUpdateAt - moment()); let duration = moment.duration(statusUpdateAt - moment());
@ -33,7 +33,7 @@ export default Ember.Component.extend(bufferedRender({
rerenderDelay = 60000; rerenderDelay = 60000;
} }
let autoCloseHours = this.get("topic.topic_status_update.duration") || 0; let autoCloseHours = this.get("duration") || 0;
buffer.push('<h3><i class="fa fa-clock-o"></i> '); buffer.push('<h3><i class="fa fa-clock-o"></i> ');
@ -42,7 +42,7 @@ export default Ember.Component.extend(bufferedRender({
duration: moment.duration(autoCloseHours, "hours").humanize(), duration: moment.duration(autoCloseHours, "hours").humanize(),
}; };
const categoryId = this.get('topic.topic_status_update.category_id'); const categoryId = this.get('categoryId');
if (categoryId) { if (categoryId) {
const category = Category.findById(categoryId); const category = Category.findById(categoryId);
@ -67,9 +67,9 @@ export default Ember.Component.extend(bufferedRender({
}, },
_noticeKey() { _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`; return `topic.status_update_notice.auto_${statusType}_based_on_last_post`;
} else { } else {
return `topic.status_update_notice.auto_${statusType}`; return `topic.status_update_notice.auto_${statusType}`;

View File

@ -3,16 +3,11 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import TopicStatusUpdate from 'discourse/models/topic-status-update'; import TopicStatusUpdate from 'discourse/models/topic-status-update';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
const CLOSE_STATUS_TYPE = 'close'; export const CLOSE_STATUS_TYPE = 'close';
const OPEN_STATUS_TYPE = 'open'; const OPEN_STATUS_TYPE = 'open';
const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category'; const PUBLISH_TO_CATEGORY_STATUS_TYPE = 'publish_to_category';
export default Ember.Controller.extend(ModalFunctionality, { 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, loading: false,
updateTime: null, updateTime: null,
topicStatusUpdate: Ember.computed.alias("model.topic_status_update"), 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), autoClose: Ember.computed.equal('selection', CLOSE_STATUS_TYPE),
publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE), publishToCategory: Ember.computed.equal('selection', PUBLISH_TO_CATEGORY_STATUS_TYPE),
@computed('autoClose', 'updateTime') @computed("model.closed")
disableAutoClose(autoClose, updateTime) { statusUpdates(closed) {
return updateTime && !autoClose; 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') @computed('updateTime', 'loading')
disableAutoOpen(autoOpen, updateTime) { saveDisabled(updateTime, loading) {
return updateTime && !autoOpen; return Ember.isEmpty(updateTime) || loading;
},
@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("model.visible") @computed("model.visible")
@ -66,29 +37,31 @@ export default Ember.Controller.extend(ModalFunctionality, {
@observes("topicStatusUpdate.execute_at", "topicStatusUpdate.duration") @observes("topicStatusUpdate.execute_at", "topicStatusUpdate.duration")
_setUpdateTime() { _setUpdateTime() {
if (!this.get('topicStatusUpdate.execute_at')) return;
let time = null; let time = null;
if (this.get("topicStatusUpdate.based_on_last_post")) { if (this.get("topicStatusUpdate.based_on_last_post")) {
time = this.get("topicStatusUpdate.duration"); time = this.get("topicStatusUpdate.duration");
} else if (this.get("topicStatusUpdate.execute_at")) { } 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()) { if (closeTime > moment()) {
time = moment(closeTime).format("YYYY-MM-DD HH:mm"); time = closeTime.format("YYYY-MM-DD HH:mm");
} }
} }
this.set("updateTime", time); this.set("updateTime", time);
}, },
_setStatusUpdate(time, status_type) { _setStatusUpdate(time, statusType) {
this.set('loading', true); this.set('loading', true);
TopicStatusUpdate.updateStatus( TopicStatusUpdate.updateStatus(
this.get('model.id'), this.get('model.id'),
time, time,
this.get('topicStatusUpdate.based_on_last_post'), this.get('topicStatusUpdate.based_on_last_post'),
status_type, statusType,
this.get('categoryId') this.get('categoryId')
).then(result => { ).then(result => {
if (time) { if (time) {
@ -102,8 +75,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.set('model.closed', result.closed); this.set('model.closed', result.closed);
} else { } else {
this.set('topicStatusUpdate', Ember.Object.create({})); this.setProperties({
this.set('selection', null); topicStatusUpdate: Ember.Object.create({}),
selection: null,
updateTime: null
});
} }
}).catch(error => { }).catch(error => {
popupAjaxError(error); popupAjaxError(error);

View File

@ -54,7 +54,7 @@ const TopicRoute = Discourse.Route.extend({
const model = this.modelFor('topic'); const model = this.modelFor('topic');
model.set('topic_status_update', Ember.Object.create(model.get('topic_status_update'))); model.set('topic_status_update', Ember.Object.create(model.get('topic_status_update')));
showModal('edit-topic-status-update', { model }); 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() { showChangeTimestamp() {

View File

@ -1,24 +1,52 @@
<div class="auto-update-input"> <div class="auto-update-input">
<div class="control-group"> <div class="control-group">
<label> <label>{{i18n "topic.topic_status_update.when"}}</label>
{{i18n inputLabelKey}}
{{text-field value=input}}
{{i18n inputUnitsKey}}
</label>
{{#if inputExamplesKey}} {{auto-update-input-selector
<div class="examples"> valueAttribute="id"
{{i18n inputExamplesKey}} minimumResultsForSearch=-1
</div> statusType=statusType
{{/if}} value=selection
input=input
width="50%"
none="topic.auto_update_input.none"}}
</div> </div>
{{#unless hideBasedOnLastPost}} {{#if isCustom}}
<div class="control-group">
{{fa-icon "calendar"}} {{date-picker-future value=date defaultDate=date}}
</div>
<div class="control-group">
{{fa-icon "clock-o"}}
{{input type="time" value=time}}
</div>
{{/if}}
{{#if isBasedOnLastPost}}
<div class="control-group"> <div class="control-group">
<label> <label>
{{input type="checkbox" checked=basedOnLastPost}} {{i18n 'topic.topic_status_update.num_of_hours'}}
{{i18n 'topic.auto_close.based_on_last_post'}} {{text-field value=input type="number"}}
</label> </label>
</div> </div>
{{/unless}}
{{#if willCloseImmediately}}
<div class="warning">
{{fa-icon "warning"}}
{{willCloseI18n}}
</div>
{{/if}}
{{/if}}
{{#if showTopicStatusInfo}}
<div class="alert alert-info">
{{topic-status-info
statusType=statusType
executeAt=executeAt
basedOnLastPost=basedOnLastPost
duration=duration
categoryId=categoryId}}
</div>
{{/if}}
</div> </div>

View File

@ -1 +1 @@
<input type="text" class="date-picker"> {{input type="text" class="date-picker" placeholder=placeholder}}

View File

@ -1,10 +1,15 @@
<section class='field'> <section class='field'>
{{auto-update-input <div class="control-group">
inputLabelKey='topic.auto_close.label' <label>
input=category.auto_close_hours {{i18n 'topic.auto_close.label'}}
basedOnLastPost=category.auto_close_based_on_last_post {{text-field value=category.auto_close_hours type="number"}}
inputExamplesKey='' </label>
limited=true}}
<label>
{{input type="checkbox" checked=category.auto_close_based_on_last_post}}
{{i18n 'topic.auto_close.based_on_last_post'}}
</label>
</div>
</section> </section>
<section class='field'> <section class='field'>

View File

@ -1,59 +1,14 @@
<form> <form>
{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}} {{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
<div class="radios"> <div class="control-group">
{{radio-button {{combo-box content=statusUpdates value=selection width="50%"}}
disabled=disableAutoClose
name="auto-close"
id="auto-close"
value=closeStatusType
selection=selection}}
<label class="radio" for="auto-close">
{{fa-icon "clock-o"}} {{fa-icon "lock"}}
{{#if model.closed}}
{{i18n 'topic.temp_open.title'}}
{{else}}
{{i18n 'topic.auto_close.title'}}
{{/if}}
</label>
{{radio-button
disabled=disableAutoOpen
name="auto-reopen"
id="auto-reopen"
value=openStatusType
selection=selection}}
<label class="radio" for="auto-reopen">
{{fa-icon "clock-o"}} {{fa-icon "unlock"}}
{{#if model.closed}}
{{i18n 'topic.auto_reopen.title'}}
{{else}}
{{i18n 'topic.temp_close.title'}}
{{/if}}
</label>
{{radio-button
disabled=disablePublishToCategory
name="publish-to-category"
id="publish-to-category"
value=publishToCategoryStatusType
selection=selection}}
<label class="radio" for="publish-to-category">
{{fa-icon "clock-o"}} {{i18n 'topic.publish_to_category.title'}}
</label>
</div> </div>
<div> <div>
{{#if autoOpen}} {{#if autoOpen}}
{{auto-update-input {{auto-update-input
inputLabelKey='topic.topic_status_update.time'
input=updateTime input=updateTime
inputValid=updateTimeValid statusType=selection
hideBasedOnLastPost=true
basedOnLastPost=false}} basedOnLastPost=false}}
{{else if publishToCategory}} {{else if publishToCategory}}
<div class="control-group"> <div class="control-group">
@ -62,25 +17,16 @@
</div> </div>
{{auto-update-input {{auto-update-input
inputLabelKey='topic.topic_status_update.time'
input=updateTime input=updateTime
inputValid=updateTimeValid statusType=selection
hideBasedOnLastPost=true categoryId=categoryId
basedOnLastPost=false}} basedOnLastPost=false}}
{{else if autoClose}} {{else if autoClose}}
{{auto-update-input {{auto-update-input
inputLabelKey='topic.topic_status_update.time'
input=updateTime input=updateTime
inputValid=updateTimeValid statusType=selection
limited=topicStatusUpdate.based_on_last_post basedOnLastPost=topicStatusUpdate.based_on_last_post
basedOnLastPost=topicStatusUpdate.based_on_last_post}} lastPostedAt=model.last_posted_at}}
{{#if willCloseImmediately}}
<div class="warning">
{{fa-icon "warning"}}
{{willCloseI18n}}
</div>
{{/if}}
{{/if}} {{/if}}
</div> </div>
{{/d-modal-body}} {{/d-modal-body}}

View File

@ -174,7 +174,13 @@
{{#conditional-loading-spinner condition=model.postStream.loadingFilter}} {{#conditional-loading-spinner condition=model.postStream.loadingFilter}}
{{#if loadedAllPosts}} {{#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}} {{#if session.showSignupCta}}
{{! replace "Log In to Reply" with the infobox }} {{! replace "Log In to Reply" with the infobox }}
{{signup-cta}} {{signup-cta}}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -16,7 +16,7 @@
.show-topic-admin, .show-topic-admin,
#topic-progress, #topic-progress,
.quote-controls, .quote-controls,
#topic-status-info, .topic-status-info,
div.lazyYT, div.lazyYT,
.post-info.edits, .post-info.edits,
.post-action, .post-action,

View File

@ -79,7 +79,7 @@
} }
} }
#topic-status-info { .topic-status-info {
border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%); border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -75%);
padding-top: 10px; padding-top: 10px;
height: 20px; height: 20px;

View File

@ -43,7 +43,7 @@
clear: both; clear: both;
} }
#topic-status-info { .topic-status-info {
margin-left: 10px; margin-left: 10px;
} }

View File

@ -127,6 +127,7 @@ en:
other: "%{count} years later" other: "%{count} years later"
previous_month: 'Previous Month' previous_month: 'Previous Month'
next_month: 'Next Month' next_month: 'Next Month'
placeholder: Pick a date
share: share:
topic: 'share a link to this topic' topic: 'share a link to this topic'
post: 'post #%{postNumber}' post: 'post #%{postNumber}'
@ -1484,16 +1485,19 @@ en:
topic_status_update: topic_status_update:
title: "Set Topic Timer" title: "Set Topic Timer"
save: "Set Timer" save: "Set Timer"
time: "Time:" num_of_hours: "Number of hours:"
remove: "Remove Timer" remove: "Remove Timer"
publish_to: "Publish To:" publish_to: "Publish To:"
when: "When:"
auto_update_input: auto_update_input:
limited: none: ""
units: "(# of hours)" later_today: "Later today"
examples: 'Enter number of hours (24).' tomorrow: "Tomorrow"
all: later_this_week: "Later this week"
units: "" this_weekend: "This weekend"
examples: 'Enter number of hours (24), absolute time (17:30) or timestamp (2013-11-22 14:00).' 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: publish_to_category:
title: "Schedule Publishing" title: "Schedule Publishing"
temp_open: temp_open:
@ -1504,7 +1508,7 @@ en:
title: "Close Temporarily" title: "Close Temporarily"
auto_close: auto_close:
title: "Auto-Close Topic" title: "Auto-Close Topic"
label: "Auto-close topic time:" label: "Auto-close topic hours:"
error: "Please enter a valid value." 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." 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" 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." 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" images: "Images"
auto_close_label: "Auto-close topics after:"
auto_close_units: "hours"
email_in: "Custom incoming email address:" email_in: "Custom incoming email address:"
email_in_allow_strangers: "Accept emails from anonymous users with no accounts" 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, " email_in_disabled: "Posting new topics via email is disabled in the Site Settings. To enable posting new topics via email, "

View File

@ -56,3 +56,19 @@ componentTest('with none', {
assert.equal(this.$("select option:eq(2)").text(), 'trout'); 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');
}
});