FEATURE: Topic timer UI revamp (#11912)
This PR revamps the topic timer UI, using the time shortcut selector from the bookmark modal. * Fixes an issue where the duration of hours/days after last reply or auto delete replies was not enforced to be > 0 * Fixed an issue where the timer dropdown options were not reloaded correctly if the topic status changes in the background (use `MessageBus` to publish topic state in the open/close timer jobs) * Moved the duration input and the "based on last post" option from the `future-date-input` component, as it was only used for topic timers. Also moved out the notice that is displayed which was also only relevant for topic timers.
This commit is contained in:
parent
f39ae8a903
commit
6d72c8ab19
|
@ -6,26 +6,27 @@ import {
|
|||
OPEN_STATUS_TYPE,
|
||||
PUBLISH_TO_CATEGORY_STATUS_TYPE,
|
||||
} from "discourse/controllers/edit-topic-timer";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import { FORMAT } from "select-kit/components/future-date-input-selector";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { equal, or, readOnly } from "@ember/object/computed";
|
||||
import I18n from "I18n";
|
||||
import { action } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { now, startOfDay, thisWeekend } from "discourse/lib/time-utils";
|
||||
|
||||
export default Component.extend({
|
||||
selection: readOnly("topicTimer.status_type"),
|
||||
autoOpen: equal("selection", OPEN_STATUS_TYPE),
|
||||
autoClose: equal("selection", CLOSE_STATUS_TYPE),
|
||||
autoDelete: equal("selection", DELETE_STATUS_TYPE),
|
||||
autoBump: equal("selection", BUMP_TYPE),
|
||||
publishToCategory: equal("selection", PUBLISH_TO_CATEGORY_STATUS_TYPE),
|
||||
autoDeleteReplies: equal("selection", DELETE_REPLIES_TYPE),
|
||||
statusType: readOnly("topicTimer.status_type"),
|
||||
autoOpen: equal("statusType", OPEN_STATUS_TYPE),
|
||||
autoClose: equal("statusType", CLOSE_STATUS_TYPE),
|
||||
autoDelete: equal("statusType", DELETE_STATUS_TYPE),
|
||||
autoBump: equal("statusType", BUMP_TYPE),
|
||||
publishToCategory: equal("statusType", PUBLISH_TO_CATEGORY_STATUS_TYPE),
|
||||
autoDeleteReplies: equal("statusType", DELETE_REPLIES_TYPE),
|
||||
showTimeOnly: or("autoOpen", "autoDelete", "autoBump"),
|
||||
showFutureDateInput: or(
|
||||
"showTimeOnly",
|
||||
"publishToCategory",
|
||||
"autoClose",
|
||||
"autoDeleteReplies"
|
||||
),
|
||||
showFutureDateInput: or("showTimeOnly", "publishToCategory", "autoClose"),
|
||||
useDuration: or("isBasedOnLastPost", "autoDeleteReplies"),
|
||||
originalTopicTimerTime: null,
|
||||
|
||||
@discourseComputed("autoDeleteReplies")
|
||||
durationType(autoDeleteReplies) {
|
||||
|
@ -39,26 +40,142 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@observes("selection")
|
||||
_updateBasedOnLastPost() {
|
||||
if (!this.autoClose) {
|
||||
schedule("afterRender", () => {
|
||||
this.set("topicTimer.based_on_last_post", false);
|
||||
});
|
||||
@discourseComputed("includeBasedOnLastPost")
|
||||
customTimeShortcutOptions(includeBasedOnLastPost) {
|
||||
return [
|
||||
{
|
||||
icon: "bed",
|
||||
id: "this_weekend",
|
||||
label: "topic.auto_update_input.this_weekend",
|
||||
time: thisWeekend(),
|
||||
timeFormatKey: "dates.time_short_day",
|
||||
},
|
||||
{
|
||||
icon: "far-clock",
|
||||
id: "two_weeks",
|
||||
label: "topic.auto_update_input.two_weeks",
|
||||
time: startOfDay(now().add(2, "weeks")),
|
||||
timeFormatKey: "dates.long_no_year",
|
||||
},
|
||||
{
|
||||
icon: "far-calendar-plus",
|
||||
id: "three_months",
|
||||
label: "topic.auto_update_input.three_months",
|
||||
time: startOfDay(now().add(3, "months")),
|
||||
timeFormatKey: "dates.long_no_year",
|
||||
},
|
||||
{
|
||||
icon: "far-calendar-plus",
|
||||
id: "six_months",
|
||||
label: "topic.auto_update_input.six_months",
|
||||
time: startOfDay(now().add(6, "months")),
|
||||
timeFormatKey: "dates.long_no_year",
|
||||
},
|
||||
{
|
||||
icon: "far-clock",
|
||||
id: "set_based_on_last_post",
|
||||
label: "topic.auto_update_input.set_based_on_last_post",
|
||||
time: null,
|
||||
timeFormatted: "",
|
||||
hidden: !includeBasedOnLastPost,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
hiddenTimeShortcutOptions() {
|
||||
return ["none", "start_of_next_business_week"];
|
||||
},
|
||||
|
||||
isCustom: equal("timerType", "custom"),
|
||||
isBasedOnLastPost: equal("timerType", "set_based_on_last_post"),
|
||||
includeBasedOnLastPost: equal("statusType", CLOSE_STATUS_TYPE),
|
||||
|
||||
@discourseComputed(
|
||||
"topicTimer.updateTime",
|
||||
"topicTimer.duration",
|
||||
"useDuration",
|
||||
"durationType"
|
||||
)
|
||||
executeAt(updateTime, duration, useDuration, durationType) {
|
||||
if (useDuration) {
|
||||
return moment().add(parseFloat(duration), durationType).format(FORMAT);
|
||||
} else {
|
||||
return updateTime;
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
@discourseComputed(
|
||||
"isBasedOnLastPost",
|
||||
"topicTimer.duration",
|
||||
"topic.last_posted_at"
|
||||
)
|
||||
willCloseImmediately(isBasedOnLastPost, duration, lastPostedAt) {
|
||||
if (isBasedOnLastPost && duration) {
|
||||
let closeDate = moment(lastPostedAt);
|
||||
closeDate = closeDate.add(duration, "hours");
|
||||
return closeDate < moment();
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: get rid of this hack
|
||||
schedule("afterRender", () => {
|
||||
if (!this.get("topicTimer.status_type")) {
|
||||
this.set(
|
||||
"topicTimer.status_type",
|
||||
this.get("timerTypes.firstObject.id")
|
||||
);
|
||||
@discourseComputed("isBasedOnLastPost", "topic.last_posted_at")
|
||||
willCloseI18n(isBasedOnLastPost, lastPostedAt) {
|
||||
if (isBasedOnLastPost) {
|
||||
const diff = Math.round(
|
||||
(new Date() - new Date(lastPostedAt)) / (1000 * 60 * 60)
|
||||
);
|
||||
return I18n.t("topic.auto_close_momentarily", { count: diff });
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("durationType")
|
||||
durationLabel(durationType) {
|
||||
return I18n.t(`topic.topic_status_update.num_of_${durationType}`);
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"statusType",
|
||||
"isCustom",
|
||||
"topicTimer.updateTime",
|
||||
"willCloseImmediately",
|
||||
"topicTimer.category_id",
|
||||
"useDuration",
|
||||
"topicTimer.duration"
|
||||
)
|
||||
showTopicStatusInfo(
|
||||
statusType,
|
||||
isCustom,
|
||||
updateTime,
|
||||
willCloseImmediately,
|
||||
categoryId,
|
||||
useDuration,
|
||||
duration
|
||||
) {
|
||||
if (!statusType || willCloseImmediately) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (statusType === PUBLISH_TO_CATEGORY_STATUS_TYPE && isEmpty(categoryId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustom && updateTime) {
|
||||
if (moment(updateTime) < moment()) {
|
||||
return false;
|
||||
}
|
||||
} else if (useDuration) {
|
||||
return duration;
|
||||
}
|
||||
|
||||
return updateTime;
|
||||
},
|
||||
|
||||
@action
|
||||
onTimeSelected(type, time) {
|
||||
this.setProperties({
|
||||
"topicTimer.based_on_last_post": type === "set_based_on_last_post",
|
||||
timerType: type,
|
||||
});
|
||||
this.onChangeInput(type, time);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,42 +1,30 @@
|
|||
import { and, empty, equal, or } from "@ember/object/computed";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import { and, empty, equal } from "@ember/object/computed";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import { FORMAT } from "select-kit/components/future-date-input-selector";
|
||||
import I18n from "I18n";
|
||||
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
||||
export default Component.extend({
|
||||
selection: null,
|
||||
date: null,
|
||||
time: null,
|
||||
includeDateTime: true,
|
||||
duration: null,
|
||||
durationType: "hours",
|
||||
isCustom: equal("selection", "pick_date_and_time"),
|
||||
isBasedOnLastPost: equal("selection", "set_based_on_last_post"),
|
||||
displayDateAndTimePicker: and("includeDateTime", "isCustom"),
|
||||
displayLabel: null,
|
||||
labelClasses: null,
|
||||
displayNumberInput: or("isBasedOnLastPost", "isBasedOnDuration"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.input) {
|
||||
if (this.basedOnLastPost) {
|
||||
this.set("selection", "set_based_on_last_post");
|
||||
} else if (this.isBasedOnDuration) {
|
||||
this.set("selection", null);
|
||||
} else {
|
||||
const datetime = moment(this.input);
|
||||
this.setProperties({
|
||||
selection: "pick_date_and_time",
|
||||
date: datetime.format("YYYY-MM-DD"),
|
||||
time: datetime.format("HH:mm"),
|
||||
});
|
||||
this._updateInput();
|
||||
}
|
||||
const datetime = moment(this.input);
|
||||
this.setProperties({
|
||||
selection: "pick_date_and_time",
|
||||
date: datetime.format("YYYY-MM-DD"),
|
||||
time: datetime.format("HH:mm"),
|
||||
});
|
||||
this._updateInput();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -59,49 +47,6 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@observes("isBasedOnLastPost")
|
||||
_updateBasedOnLastPost() {
|
||||
this.set("basedOnLastPost", this.isBasedOnLastPost);
|
||||
},
|
||||
|
||||
@observes("duration")
|
||||
_updateDuration() {
|
||||
this.attrs.onChangeDuration &&
|
||||
this.attrs.onChangeDuration(parseInt(this.duration, 0));
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"input",
|
||||
"duration",
|
||||
"isBasedOnLastPost",
|
||||
"isBasedOnDuration",
|
||||
"durationType"
|
||||
)
|
||||
executeAt(
|
||||
input,
|
||||
duration,
|
||||
isBasedOnLastPost,
|
||||
isBasedOnDuration,
|
||||
durationType
|
||||
) {
|
||||
if (isBasedOnLastPost || isBasedOnDuration) {
|
||||
return moment(input)
|
||||
.add(parseInt(duration, 0), durationType)
|
||||
.format(FORMAT);
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("durationType")
|
||||
durationLabel(durationType) {
|
||||
return I18n.t(
|
||||
`topic.topic_status_update.num_of_${
|
||||
durationType === "hours" ? "hours" : "days"
|
||||
}`
|
||||
);
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
|
@ -109,65 +54,4 @@ export default Component.extend({
|
|||
this.set("displayLabel", I18n.t(this.label));
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"statusType",
|
||||
"input",
|
||||
"isCustom",
|
||||
"date",
|
||||
"time",
|
||||
"willCloseImmediately",
|
||||
"categoryId",
|
||||
"displayNumberInput",
|
||||
"duration"
|
||||
)
|
||||
showTopicStatusInfo(
|
||||
statusType,
|
||||
input,
|
||||
isCustom,
|
||||
date,
|
||||
time,
|
||||
willCloseImmediately,
|
||||
categoryId,
|
||||
displayNumberInput,
|
||||
duration
|
||||
) {
|
||||
if (!statusType || willCloseImmediately) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (statusType === PUBLISH_TO_CATEGORY_STATUS_TYPE && isEmpty(categoryId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustom) {
|
||||
if (date) {
|
||||
return moment(`${date}${time ? " " + time : ""}`).isAfter(moment());
|
||||
}
|
||||
return time;
|
||||
} else if (displayNumberInput) {
|
||||
return duration;
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("isBasedOnLastPost", "input", "lastPostedAt")
|
||||
willCloseImmediately(isBasedOnLastPost, input, lastPostedAt) {
|
||||
if (isBasedOnLastPost && input) {
|
||||
let closeDate = moment(lastPostedAt);
|
||||
closeDate = closeDate.add(input, "hours");
|
||||
return closeDate < moment();
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -92,33 +92,50 @@ export default Component.extend({
|
|||
});
|
||||
|
||||
if (this.prefilledDatetime) {
|
||||
let parsedDatetime = parseCustomDatetime(
|
||||
this.prefilledDatetime,
|
||||
null,
|
||||
this.userTimezone
|
||||
);
|
||||
|
||||
if (parsedDatetime.isSame(laterToday())) {
|
||||
return this.set("selectedShortcut", TIME_SHORTCUT_TYPES.LATER_TODAY);
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
customDate: parsedDatetime.format("YYYY-MM-DD"),
|
||||
customTime: parsedDatetime.format("HH:mm"),
|
||||
selectedShortcut: TIME_SHORTCUT_TYPES.CUSTOM,
|
||||
});
|
||||
this.parsePrefilledDatetime();
|
||||
}
|
||||
|
||||
this._bindKeyboardShortcuts();
|
||||
this._loadLastUsedCustomDatetime();
|
||||
},
|
||||
|
||||
@observes("prefilledDatetime")
|
||||
prefilledDatetimeChanged() {
|
||||
if (this.prefilledDatetime) {
|
||||
this.parsePrefilledDatetime();
|
||||
} else {
|
||||
this.setProperties({
|
||||
customDate: null,
|
||||
customTime: null,
|
||||
selectedShortcut: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_resetKeyboardShortcuts() {
|
||||
KeyboardShortcuts.unbind(BINDINGS);
|
||||
KeyboardShortcuts.unpause(GLOBAL_SHORTCUTS_TO_PAUSE);
|
||||
},
|
||||
|
||||
parsePrefilledDatetime() {
|
||||
let parsedDatetime = parseCustomDatetime(
|
||||
this.prefilledDatetime,
|
||||
null,
|
||||
this.userTimezone
|
||||
);
|
||||
|
||||
if (parsedDatetime.isSame(laterToday())) {
|
||||
return this.set("selectedShortcut", TIME_SHORTCUT_TYPES.LATER_TODAY);
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
customDate: parsedDatetime.format("YYYY-MM-DD"),
|
||||
customTime: parsedDatetime.format("HH:mm"),
|
||||
selectedShortcut: TIME_SHORTCUT_TYPES.CUSTOM,
|
||||
});
|
||||
},
|
||||
|
||||
_loadLastUsedCustomDatetime() {
|
||||
let lastTime = localStorage.lastCustomTime;
|
||||
let lastDate = localStorage.lastCustomDate;
|
||||
|
@ -196,6 +213,12 @@ export default Component.extend({
|
|||
lastCustom.hidden = false;
|
||||
}
|
||||
|
||||
customOptions.forEach((opt) => {
|
||||
if (!opt.timeFormatted && opt.time) {
|
||||
opt.timeFormatted = opt.time.format(I18n.t(opt.timeFormatKey));
|
||||
}
|
||||
});
|
||||
|
||||
let customOptionIndex = options.findIndex(
|
||||
(opt) => opt.id === TIME_SHORTCUT_TYPES.CUSTOM
|
||||
);
|
||||
|
|
|
@ -39,6 +39,10 @@ export default Component.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topicStatus = this.topicClosed ? "close" : "open";
|
||||
const topicStatusKnown = this.topicClosed !== undefined;
|
||||
if (topicStatusKnown && topicStatus === this.statusType) {
|
||||
|
|
|
@ -83,11 +83,12 @@ export default Controller.extend(ModalFunctionality, {
|
|||
|
||||
this.set("model.closed", result.closed);
|
||||
} else {
|
||||
this.set("model.topic_timer", EmberObject.create({}));
|
||||
this.set(
|
||||
"model.topic_timer",
|
||||
EmberObject.create({ status_type: this.defaultStatusType })
|
||||
);
|
||||
|
||||
this.setProperties({
|
||||
selection: null,
|
||||
});
|
||||
this.send("onChangeInput", null, null);
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
|
@ -106,7 +107,16 @@ export default Controller.extend(ModalFunctionality, {
|
|||
}
|
||||
}
|
||||
|
||||
this.send("onChangeInput", time);
|
||||
this.send("onChangeInput", null, time);
|
||||
|
||||
if (!this.get("topicTimer.status_type")) {
|
||||
this.send("onChangeStatusType", this.defaultStatusType);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("publicTimerTypes")
|
||||
defaultStatusType(publicTimerTypes) {
|
||||
return publicTimerTypes[0].id;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
@ -114,8 +124,11 @@ export default Controller.extend(ModalFunctionality, {
|
|||
this.set("topicTimer.status_type", value);
|
||||
},
|
||||
|
||||
onChangeInput(value) {
|
||||
this.set("topicTimer.updateTime", value);
|
||||
onChangeInput(_type, time) {
|
||||
if (moment.isMoment(time)) {
|
||||
time = time.format(FORMAT);
|
||||
}
|
||||
this.set("topicTimer.updateTime", time);
|
||||
},
|
||||
|
||||
onChangeDuration(value) {
|
||||
|
@ -134,6 +147,18 @@ export default Controller.extend(ModalFunctionality, {
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.get("topicTimer.duration") &&
|
||||
!this.get("topicTimer.updateTime") &&
|
||||
this.get("topicTimer.duration") < 1
|
||||
) {
|
||||
this.flash(
|
||||
I18n.t("topic.topic_status_update.min_duration"),
|
||||
"alert-error"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._setTimer(
|
||||
this.get("topicTimer.updateTime"),
|
||||
this.get("topicTimer.duration"),
|
||||
|
|
|
@ -5,6 +5,7 @@ export const LATER_TODAY_CUTOFF_HOUR = 17;
|
|||
export const LATER_TODAY_MAX_HOUR = 18;
|
||||
export const MOMENT_MONDAY = 1;
|
||||
export const MOMENT_THURSDAY = 4;
|
||||
export const MOMENT_SATURDAY = 6;
|
||||
|
||||
export function now(timezone) {
|
||||
return moment.tz(timezone);
|
||||
|
@ -18,6 +19,10 @@ export function tomorrow(timezone) {
|
|||
return startOfDay(now(timezone).add(1, "day"));
|
||||
}
|
||||
|
||||
export function thisWeekend(timezone) {
|
||||
return startOfDay(now(timezone).day(MOMENT_SATURDAY));
|
||||
}
|
||||
|
||||
export function laterToday(timezone) {
|
||||
let later = now(timezone).add(3, "hours");
|
||||
if (later.hour() >= LATER_TODAY_MAX_HOUR) {
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
class="timer-type"
|
||||
onChange=onChangeStatusType
|
||||
content=timerTypes
|
||||
value=selection
|
||||
value=statusType
|
||||
}}
|
||||
</div>
|
||||
{{#if publishToCategory}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "topic.topic_status_update.publish_to"}}</label>
|
||||
<label class="control-label">{{i18n "topic.topic_status_update.publish_to"}}</label>
|
||||
{{category-chooser
|
||||
value=topicTimer.category_id
|
||||
excludeCategoryId=excludeCategoryId
|
||||
|
@ -18,21 +18,31 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
{{#if showFutureDateInput}}
|
||||
<div class="control-group">
|
||||
{{future-date-input
|
||||
input=(readonly topicTimer.updateTime)
|
||||
duration=(readonly topicTimer.duration)
|
||||
label="topic.topic_status_update.when"
|
||||
statusType=selection
|
||||
includeWeekend=true
|
||||
<label class="control-label">{{i18n "topic.topic_status_update.when"}}</label>
|
||||
{{time-shortcut-picker prefilledDatetime=topicTimer.execute_at onTimeSelected=onTimeSelected customOptions=customTimeShortcutOptions hiddenOptions=hiddenTimeShortcutOptions}}
|
||||
{{/if}}
|
||||
{{#if useDuration}}
|
||||
<div class="controls">
|
||||
<label class="control-label">{{durationLabel}}</label>
|
||||
{{text-field id="topic_timer_duration" class="topic-timer-duration" type="number" value=topicTimer.duration min="1"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if willCloseImmediately}}
|
||||
<div class="warning">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
{{willCloseI18n}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if showTopicStatusInfo}}
|
||||
<div class="alert alert-info">
|
||||
{{topic-timer-info
|
||||
statusType=statusType
|
||||
executeAt=executeAt
|
||||
basedOnLastPost=topicTimer.based_on_last_post
|
||||
onChangeInput=onChangeInput
|
||||
onChangeDuration=onChangeDuration
|
||||
duration=topicTimer.duration
|
||||
categoryId=topicTimer.category_id
|
||||
lastPostedAt=model.last_posted_at
|
||||
isBasedOnDuration=autoDeleteReplies
|
||||
durationType=durationType
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
<div class="future-date-input">
|
||||
{{#unless isBasedOnDuration}}
|
||||
<div class="control-group">
|
||||
<label class={{labelClasses}}>{{displayLabel}}</label>
|
||||
{{future-date-input-selector
|
||||
minimumResultsForSearch=-1
|
||||
statusType=statusType
|
||||
value=(readonly selection)
|
||||
input=(readonly input)
|
||||
includeDateTime=includeDateTime
|
||||
includeWeekend=includeWeekend
|
||||
includeFarFuture=includeFarFuture
|
||||
includeMidFuture=includeMidFuture
|
||||
includeNow=includeNow
|
||||
clearable=clearable
|
||||
onChangeInput=onChangeInput
|
||||
onChange=(action (mut selection))
|
||||
options=(hash
|
||||
none="topic.auto_update_input.none"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="control-group">
|
||||
<label class={{labelClasses}}>{{displayLabel}}</label>
|
||||
{{future-date-input-selector
|
||||
minimumResultsForSearch=-1
|
||||
statusType=statusType
|
||||
value=(readonly selection)
|
||||
input=(readonly input)
|
||||
includeDateTime=includeDateTime
|
||||
includeWeekend=includeWeekend
|
||||
includeFarFuture=includeFarFuture
|
||||
includeMidFuture=includeMidFuture
|
||||
includeNow=includeNow
|
||||
clearable=clearable
|
||||
onChangeInput=onChangeInput
|
||||
onChange=(action (mut selection))
|
||||
options=(hash
|
||||
none="topic.auto_update_input.none"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
|
||||
{{#if displayDateAndTimePicker}}
|
||||
<div class="control-group">
|
||||
|
@ -37,32 +35,4 @@
|
|||
{{input placeholder="--:--" type="time" class="time-input" value=time disabled=timeInputDisabled}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if displayNumberInput}}
|
||||
<div class="control-group">
|
||||
<label>
|
||||
{{durationLabel}}
|
||||
{{text-field value=duration type="number"}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{#if willCloseImmediately}}
|
||||
<div class="warning">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
{{willCloseI18n}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showTopicStatusInfo}}
|
||||
<div class="alert alert-info">
|
||||
{{topic-timer-info
|
||||
statusType=statusType
|
||||
executeAt=executeAt
|
||||
basedOnLastPost=basedOnLastPost
|
||||
duration=duration
|
||||
categoryId=categoryId
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
|
||||
{{#d-modal-body title="topic.topic_status_update.title" autoFocus="false" id="topic-timer-modal"}}
|
||||
{{edit-topic-timer-form
|
||||
topic=model
|
||||
topicTimer=topicTimer
|
||||
timerTypes=publicTimerTypes
|
||||
updateTime=updateTime
|
||||
onChangeStatusType=(action "onChangeStatusType")
|
||||
onChangeInput=(action "onChangeInput")
|
||||
onChangeDuration=(action "onChangeDuration")
|
||||
}}
|
||||
|
||||
<div class="modal-footer control-group edit-topic-timer-buttons">
|
||||
{{d-button class="btn-primary"
|
||||
disabled=saveDisabled
|
||||
label="topic.topic_status_update.save"
|
||||
action=(action "saveTimer")}}
|
||||
|
||||
{{conditional-loading-spinner size="small" condition=loading}}
|
||||
|
||||
{{#if topicTimer.execute_at}}
|
||||
{{d-button class="pull-right btn-danger"
|
||||
action=(action "removeTimer")
|
||||
label="topic.topic_status_update.remove"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary"
|
||||
disabled=saveDisabled
|
||||
label="topic.topic_status_update.save"
|
||||
action=(action "saveTimer")}}
|
||||
|
||||
{{conditional-loading-spinner size="small" condition=loading}}
|
||||
|
||||
{{#if topicTimer.execute_at}}
|
||||
{{d-button class="pull-right btn-danger"
|
||||
action=(action "removeTimer")
|
||||
label="topic.topic_status_update.remove"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
updateCurrentUser,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||
import { skip, test } from "qunit";
|
||||
import { test } from "qunit";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
|
||||
acceptance("Topic - Edit timer", function (needs) {
|
||||
|
@ -24,108 +24,56 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
);
|
||||
});
|
||||
|
||||
test("default", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
await click(".topic-admin-status-update button");
|
||||
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().label(),
|
||||
"Select a timeframe"
|
||||
);
|
||||
assert.equal(futureDateInputSelector.header().value(), null);
|
||||
});
|
||||
|
||||
test("autoclose - specific time", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
await click(".topic-admin-status-update button");
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("next_week");
|
||||
|
||||
assert.ok(futureDateInputSelector.header().label().includes("Next week"));
|
||||
assert.equal(futureDateInputSelector.header().value(), "next_week");
|
||||
await click("#tap_tile_next_week");
|
||||
|
||||
const regex = /will automatically close in/g;
|
||||
const html = queryAll(".future-date-input .topic-status-info")
|
||||
const html = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex.test(html));
|
||||
});
|
||||
|
||||
skip("autoclose", async function (assert) {
|
||||
test("autoclose", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
await click(".topic-admin-status-update button");
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("next_week");
|
||||
|
||||
assert.ok(futureDateInputSelector.header().label().includes("Next week"));
|
||||
assert.equal(futureDateInputSelector.header().value(), "next_week");
|
||||
await click("#tap_tile_next_week");
|
||||
|
||||
const regex1 = /will automatically close in/g;
|
||||
const html1 = queryAll(".future-date-input .topic-status-info")
|
||||
const html1 = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex1.test(html1));
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("pick_date_and_time");
|
||||
|
||||
await fillIn(".future-date-input .date-picker", "2099-11-24");
|
||||
|
||||
assert.ok(
|
||||
futureDateInputSelector.header().label().includes("Pick date and time")
|
||||
);
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().value(),
|
||||
"pick_date_and_time"
|
||||
);
|
||||
await click("#tap_tile_custom");
|
||||
await fillIn(".tap-tile-date-input .date-picker", "2099-11-24");
|
||||
|
||||
const regex2 = /will automatically close in/g;
|
||||
const html2 = queryAll(".future-date-input .topic-status-info")
|
||||
const html2 = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex2.test(html2));
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("set_based_on_last_post");
|
||||
await click("#tap_tile_set_based_on_last_post");
|
||||
await fillIn("#topic_timer_duration", "2");
|
||||
|
||||
await fillIn(".future-date-input input[type=number]", "2");
|
||||
|
||||
assert.ok(
|
||||
futureDateInputSelector
|
||||
.header()
|
||||
.label()
|
||||
.includes("Close based on last post")
|
||||
);
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().value(),
|
||||
"set_based_on_last_post"
|
||||
);
|
||||
|
||||
const regex3 = /This topic will close.*after the last reply/g;
|
||||
const html3 = queryAll(".future-date-input .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
const regex3 = /last post in the topic is already/g;
|
||||
const html3 = queryAll(".edit-topic-timer-modal .warning").html().trim();
|
||||
assert.ok(regex3.test(html3));
|
||||
});
|
||||
|
||||
test("close temporarily", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const timerType = selectKit(".select-kit.timer-type");
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
|
@ -134,40 +82,19 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
await timerType.expand();
|
||||
await timerType.selectRowByValue("open");
|
||||
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().label(),
|
||||
"Select a timeframe"
|
||||
);
|
||||
assert.equal(futureDateInputSelector.header().value(), null);
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("next_week");
|
||||
|
||||
assert.ok(futureDateInputSelector.header().label().includes("Next week"));
|
||||
assert.equal(futureDateInputSelector.header().value(), "next_week");
|
||||
await click("#tap_tile_next_week");
|
||||
|
||||
const regex1 = /will automatically open in/g;
|
||||
const html1 = queryAll(".future-date-input .topic-status-info")
|
||||
const html1 = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex1.test(html1));
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("pick_date_and_time");
|
||||
|
||||
await fillIn(".future-date-input .date-picker", "2099-11-24");
|
||||
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().label(),
|
||||
"Pick date and time"
|
||||
);
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().value(),
|
||||
"pick_date_and_time"
|
||||
);
|
||||
await click("#tap_tile_custom");
|
||||
await fillIn(".tap-tile-date-input .date-picker", "2099-11-24");
|
||||
|
||||
const regex2 = /will automatically open in/g;
|
||||
const html2 = queryAll(".future-date-input .topic-status-info")
|
||||
const html2 = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex2.test(html2));
|
||||
|
@ -177,7 +104,6 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
updateCurrentUser({ moderator: true });
|
||||
const timerType = selectKit(".select-kit.timer-type");
|
||||
const categoryChooser = selectKit(".modal-body .category-chooser");
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
|
@ -189,23 +115,13 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
assert.equal(categoryChooser.header().label(), "uncategorized");
|
||||
assert.equal(categoryChooser.header().value(), null);
|
||||
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().label(),
|
||||
"Select a timeframe"
|
||||
);
|
||||
assert.equal(futureDateInputSelector.header().value(), null);
|
||||
|
||||
await categoryChooser.expand();
|
||||
await categoryChooser.selectRowByValue("7");
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("next_week");
|
||||
|
||||
assert.ok(futureDateInputSelector.header().label().includes("Next week"));
|
||||
assert.equal(futureDateInputSelector.header().value(), "next_week");
|
||||
await click("#tap_tile_next_week");
|
||||
|
||||
const regex = /will be published to #dev/g;
|
||||
const text = queryAll(".future-date-input .topic-status-info")
|
||||
const text = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.text()
|
||||
.trim();
|
||||
assert.ok(regex.test(text));
|
||||
|
@ -228,7 +144,6 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
test("auto delete", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const timerType = selectKit(".select-kit.timer-type");
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
|
@ -237,20 +152,10 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
await timerType.expand();
|
||||
await timerType.selectRowByValue("delete");
|
||||
|
||||
assert.equal(
|
||||
futureDateInputSelector.header().label(),
|
||||
"Select a timeframe"
|
||||
);
|
||||
assert.equal(futureDateInputSelector.header().value(), null);
|
||||
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("two_weeks");
|
||||
|
||||
assert.ok(futureDateInputSelector.header().label().includes("Two Weeks"));
|
||||
assert.equal(futureDateInputSelector.header().value(), "two_weeks");
|
||||
await click("#tap_tile_two_weeks");
|
||||
|
||||
const regex = /will be automatically deleted/g;
|
||||
const html = queryAll(".future-date-input .topic-status-info")
|
||||
const html = queryAll(".edit-topic-timer-modal .topic-status-info")
|
||||
.html()
|
||||
.trim();
|
||||
assert.ok(regex.test(html));
|
||||
|
@ -258,14 +163,12 @@ acceptance("Topic - Edit timer", function (needs) {
|
|||
|
||||
test("Inline delete timer", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
const futureDateInputSelector = selectKit(".future-date-input-selector");
|
||||
|
||||
await visit("/t/internationalization-localization");
|
||||
await click(".toggle-admin-menu");
|
||||
await click(".topic-admin-status-update button");
|
||||
await futureDateInputSelector.expand();
|
||||
await futureDateInputSelector.selectRowByValue("next_week");
|
||||
await click(".modal-footer button.btn-primary");
|
||||
await click("#tap_tile_next_week");
|
||||
await click(".edit-topic-timer-buttons button.btn-primary");
|
||||
|
||||
const removeTimerButton = queryAll(
|
||||
".topic-status-info .topic-timer-remove"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { CLOSE_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
|
||||
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||
import DatetimeMixin from "select-kit/components/future-date-input-selector/mixin";
|
||||
import I18n from "I18n";
|
||||
|
@ -125,11 +124,6 @@ export const TIMEFRAMES = [
|
|||
enabled: (opts) => opts.includeDateTime,
|
||||
icon: "far-calendar-plus",
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "set_based_on_last_post",
|
||||
enabled: (opts) => opts.includeBasedOnLastPost,
|
||||
icon: "far-clock",
|
||||
}),
|
||||
];
|
||||
|
||||
let _timeframeById = null;
|
||||
|
@ -147,7 +141,6 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
|||
pluginApiIdentifiers: ["future-date-input-selector"],
|
||||
classNames: ["future-date-input-selector"],
|
||||
isCustom: equal("value", "pick_date_and_time"),
|
||||
isBasedOnLastPost: equal("value", "set_based_on_last_post"),
|
||||
|
||||
selectKitOptions: {
|
||||
autoInsertNoneItem: false,
|
||||
|
@ -168,7 +161,6 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
|||
includeMidFuture: this.includeMidFuture || true,
|
||||
includeFarFuture: this.includeFarFuture,
|
||||
includeDateTime: this.includeDateTime,
|
||||
includeBasedOnLastPost: this.statusType === CLOSE_STATUS_TYPE,
|
||||
canScheduleNow: this.includeNow || false,
|
||||
canScheduleToday: 24 - now.hour() > 6,
|
||||
};
|
||||
|
@ -185,7 +177,7 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
|||
|
||||
actions: {
|
||||
onChange(value) {
|
||||
if (value !== "pick_date_and_time" || !this.isBasedOnLastPost) {
|
||||
if (value !== "pick_date_and_time") {
|
||||
const { time } = this._updateAt(value);
|
||||
if (time && !isEmpty(value)) {
|
||||
this.attrs.onChangeInput &&
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@import "directory";
|
||||
@import "discourse";
|
||||
@import "edit-category";
|
||||
@import "edit-topic-status-update-modal";
|
||||
@import "edit-topic-timer-modal";
|
||||
@import "ember-select";
|
||||
@import "emoji";
|
||||
@import "exception";
|
||||
|
|
|
@ -1,33 +1,34 @@
|
|||
.edit-topic-timer-modal {
|
||||
.select-kit.combo-box {
|
||||
width: 100%;
|
||||
}
|
||||
.modal-footer {
|
||||
margin: 0;
|
||||
border-top: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.modal-body {
|
||||
max-height: none;
|
||||
overflow: visible !important; /* inline JS styles */
|
||||
}
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 375px;
|
||||
|
||||
> .d-icon {
|
||||
margin-right: 5px;
|
||||
.control-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
input.date-picker,
|
||||
input[type="time"] {
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
}
|
||||
.radios {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
label {
|
||||
display: inline-flex;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 0;
|
||||
align-items: center;
|
||||
input {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.topic-timer-duration {
|
||||
width: 100%;
|
||||
}
|
||||
.btn.pull-right {
|
||||
margin-right: 10px;
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
@import "svg";
|
||||
@import "tap-tile";
|
||||
@import "time-input";
|
||||
@import "time-shortcut-picker";
|
||||
@import "user-card";
|
||||
@import "user-info";
|
||||
@import "user-stream-item";
|
||||
|
|
|
@ -31,39 +31,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.custom-date-time-wrap {
|
||||
padding: 1em 1em 0.5em;
|
||||
border: 1px solid var(--primary-low);
|
||||
border-top: none;
|
||||
background: var(--primary-very-low);
|
||||
.d-icon {
|
||||
padding: 0 0.75em 0 0;
|
||||
color: var(--primary-high);
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
|
||||
.tap-tile-date-input,
|
||||
.tap-tile-time-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
input {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.date-picker,
|
||||
.time-input {
|
||||
text-align: left;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.time-input,
|
||||
.date-picker-wrapper {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.bookmark-name-wrap {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
.custom-date-time-wrap {
|
||||
padding: 1em 1em 0.5em;
|
||||
border: 1px solid var(--primary-low);
|
||||
border-top: none;
|
||||
background: var(--primary-very-low);
|
||||
.d-icon {
|
||||
padding: 0 0.75em 0 0;
|
||||
color: var(--primary-high);
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
|
||||
.tap-tile-date-input,
|
||||
.tap-tile-time-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
input {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.date-picker,
|
||||
.time-input {
|
||||
text-align: left;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.time-input,
|
||||
.date-picker-wrapper {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
|
@ -208,6 +208,9 @@ sub sub {
|
|||
.btn.pull-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
.modal-body {
|
||||
width: 375px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-footer-main-buttons {
|
||||
|
|
|
@ -24,6 +24,8 @@ module Jobs
|
|||
|
||||
# this handles deleting the topic timer as wel, see TopicStatusUpdater
|
||||
topic.update_status('autoclosed', true, user, { silent: silent })
|
||||
|
||||
MessageBus.publish("/topic/#{topic.id}", reload_topic: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,8 @@ module Jobs
|
|||
end
|
||||
|
||||
topic.inherit_auto_close_from_category(timer_type: :close)
|
||||
|
||||
MessageBus.publish("/topic/#{topic.id}", reload_topic: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2402,7 +2402,8 @@ en:
|
|||
remove: "Remove Timer"
|
||||
publish_to: "Publish To:"
|
||||
when: "When:"
|
||||
time_frame_required: Please select a time frame
|
||||
time_frame_required: "Please select a time frame"
|
||||
min_duration: "Duration must be greater than 0"
|
||||
auto_update_input:
|
||||
none: "Select a timeframe"
|
||||
now: "Now"
|
||||
|
@ -2456,6 +2457,9 @@ en:
|
|||
auto_close_immediate:
|
||||
one: "The last post in the topic is already %{count} hour old, so the topic will be closed immediately."
|
||||
other: "The last post in the topic is already %{count} hours old, so the topic will be closed immediately."
|
||||
auto_close_momentarily:
|
||||
one: "The last post in the topic is already %{count} hour old, so the topic will be closed momentarily."
|
||||
other: "The last post in the topic is already %{count} hours old, so the topic will be closed momentarily."
|
||||
|
||||
timeline:
|
||||
back: "Back"
|
||||
|
|
|
@ -24,6 +24,14 @@ describe Jobs::CloseTopic do
|
|||
end
|
||||
end
|
||||
|
||||
it "publishes to the topic message bus so the topic status reloads" do
|
||||
MessageBus.expects(:publish).at_least_once
|
||||
MessageBus.expects(:publish).with("/topic/#{topic.id}", reload_topic: true).once
|
||||
freeze_time(61.minutes.from_now) do
|
||||
described_class.new.execute(topic_timer_id: topic.public_topic_timer.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when trying to close a topic that has already been closed' do
|
||||
it 'should delete the topic timer' do
|
||||
freeze_time(topic.public_topic_timer.execute_at + 1.minute)
|
||||
|
|
|
@ -25,6 +25,14 @@ describe Jobs::OpenTopic do
|
|||
end
|
||||
end
|
||||
|
||||
it "publishes to the topic message bus so the topic status reloads" do
|
||||
MessageBus.expects(:publish).at_least_once
|
||||
MessageBus.expects(:publish).with("/topic/#{topic.id}", reload_topic: true).once
|
||||
freeze_time(61.minutes.from_now) do
|
||||
described_class.new.execute(topic_timer_id: topic.public_topic_timer.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when category has auto close configured' do
|
||||
fab!(:category) do
|
||||
Fabricate(:category,
|
||||
|
|
Loading…
Reference in New Issue