FIX: Clean up topic-timer-info CSS classes, add edit button, and improve UI (#12080)

This PR adds an edit button to the topic timer info message which opens the modal.

Also, I have cleaned up a few more places where we were referencing "topic status update" which is what these were called prior to being called topic timers.

The category settings for auto-close topic hours has now also been modified to use the new relative-time-picker component.

Finally, the relative-time-picker input step and min is dynamic based on mins/other intervals selected, see https://review.discourse.org/t/feature-relative-time-input-for-timers-and-bookmarks-and-promote-auto-close-after-last-post-timer-12063/19204/7?u=martin
This commit is contained in:
Martin Brennan 2021-02-16 12:07:30 +10:00 committed by GitHub
parent f80e6a2357
commit c0c7c237aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 179 additions and 90 deletions

View File

@ -5,6 +5,7 @@ import { SEARCH_PRIORITIES } from "discourse/lib/constants";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import discourseComputed from "discourse-common/utils/decorators";
import { setting } from "discourse/lib/computed";
import { action } from "@ember/object";
const categorySortCriteria = [];
export function addCategorySortCriteria(criteria) {
@ -126,4 +127,15 @@ export default buildCategoryPanel("settings", {
{ name: I18n.t("category.sort_descending"), value: false },
];
},
@discourseComputed
hiddenRelativeIntervals() {
return ["mins"];
},
@action
onAutoCloseDurationChange(minutes) {
let hours = minutes / 60;
this.set("category.auto_close_hours", hours);
},
});

View File

@ -153,7 +153,7 @@ export default Component.extend({
"useDuration",
"topicTimer.duration_minutes"
)
showTopicStatusInfo(
showTopicTimerInfo(
statusType,
isCustom,
updateTime,

View File

@ -8,11 +8,53 @@ export default Component.extend({
tagName: "",
selectedInterval: "mins",
durationMinutes: null,
durationHours: null,
duration: null,
hiddenIntervals: null,
@on("init")
cloneDuration() {
let mins = this.durationMinutes;
let hours = this.durationHours;
if (hours && mins) {
throw new Error(
"relative-time needs initial duration in hours OR minutes, both are not supported"
);
}
if (hours) {
this._setInitialDurationFromHours(hours);
} else {
this._setInitialDurationFromMinutes(mins);
}
},
@on("init")
setHiddenIntervals() {
this.hiddenIntervals = this.hiddenIntervals || [];
},
_setInitialDurationFromHours(hours) {
if (hours >= 730) {
this.setProperties({
duration: Math.floor(hours / 30 / 24),
selectedInterval: "months",
});
} else if (hours >= 24) {
this.setProperties({
duration: Math.floor(hours / 24),
selectedInterval: "days",
});
} else {
this.setProperties({
duration: hours,
selectedInterval: "hours",
});
}
},
_setInitialDurationFromMinutes(mins) {
if (mins >= 43800) {
this.setProperties({
duration: Math.floor(mins / 30 / 60 / 24),
@ -36,6 +78,16 @@ export default Component.extend({
}
},
@discourseComputed("selectedInterval")
durationMin(selectedInterval) {
return selectedInterval === "mins" ? 1 : 0.1;
},
@discourseComputed("selectedInterval")
durationStep(selectedInterval) {
return selectedInterval === "mins" ? 1 : 0.05;
},
@discourseComputed("duration")
intervals(duration) {
const count = duration ? parseFloat(duration) : 0;
@ -57,7 +109,7 @@ export default Component.extend({
id: "months",
name: I18n.t("relative_time_picker.months", { count }),
},
];
].filter((interval) => !this.hiddenIntervals.includes(interval.id));
},
@discourseComputed("selectedInterval", "duration")
@ -68,7 +120,8 @@ export default Component.extend({
switch (interval) {
case "mins":
mins = duration;
// we round up here in case the user manually inputted a step < 1
mins = Math.ceil(duration);
break;
case "hours":
mins = duration * 60;

View File

@ -3,28 +3,46 @@ import Category from "discourse/models/category";
import Component from "@ember/component";
import { DELETE_REPLIES_TYPE } from "discourse/controllers/edit-topic-timer";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import { iconHTML } from "discourse-common/lib/icon-library";
import { isTesting } from "discourse-common/config/environment";
export default Component.extend({
classNames: ["topic-status-info"],
classNames: ["topic-timer-info"],
_delayedRerender: null,
clockIcon: `${iconHTML("far-clock")}`.htmlSafe(),
trashCanIcon: `${iconHTML("trash-alt")}`.htmlSafe(),
trashCanTitle: I18n.t("post.controls.remove_timer"),
trashLabel: I18n.t("post.controls.remove_timer"),
title: null,
notice: null,
showTopicTimer: null,
showTopicTimerModal: null,
removeTopicTimer: null,
@on("didReceiveAttrs")
setupRenderer() {
this.renderTopicTimer();
},
@on("willDestroyElement")
cancelDelayedRenderer() {
if (this._delayedRerender) {
cancel(this._delayedRerender);
}
},
@discourseComputed
canRemoveTimer() {
canModifyTimer() {
return this.currentUser && this.currentUser.get("canManageTopic");
},
@discourseComputed("canRemoveTimer", "removeTopicTimer")
showTrashCan(canRemoveTimer, removeTopicTimer) {
return canRemoveTimer && removeTopicTimer;
@discourseComputed("canModifyTimer", "removeTopicTimer")
showTrashCan(canModifyTimer, removeTopicTimer) {
return canModifyTimer && removeTopicTimer;
},
@discourseComputed("canModifyTimer", "showTopicTimerModal")
showEdit(canModifyTimer, showTopicTimerModal) {
return canModifyTimer && showTopicTimerModal;
},
renderTopicTimer() {
@ -102,31 +120,6 @@ export default Component.extend({
}
},
didReceiveAttrs() {
this._super(...arguments);
this.renderTopicTimer();
},
didInsertElement() {
this._super(...arguments);
if (this.removeTopicTimer) {
$(this.element).on(
"click.topic-timer-remove",
"button",
this.removeTopicTimer
);
}
},
willDestroyElement() {
$(this.element).off("click.topic-timer-remove", this.removeTopicTimer);
if (this._delayedRerender) {
cancel(this._delayedRerender);
}
},
_noticeKey() {
let statusType = this.statusType;
if (statusType === "silent_close") {

View File

@ -29,14 +29,16 @@ export default Controller.extend(ModalFunctionality, {
closed ? "topic.temp_open.title" : "topic.auto_close.title"
),
},
{
];
if (!closed) {
types.push({
id: CLOSE_AFTER_LAST_POST_STATUS_TYPE,
name: I18n.t(
closed
? "topic.temp_open.title"
: "topic.auto_close_after_last_post.title"
),
},
name: I18n.t("topic.auto_close_after_last_post.title"),
});
}
types.push(
{
id: OPEN_STATUS_TYPE,
name: I18n.t(
@ -50,8 +52,9 @@ export default Controller.extend(ModalFunctionality, {
{
id: BUMP_TYPE,
name: I18n.t("topic.auto_bump.title"),
},
];
}
);
if (this.currentUser.get("staff")) {
types.push(
{

View File

@ -105,7 +105,7 @@ const TopicRoute = DiscourseRoute.extend({
});
},
showTopicStatusUpdate() {
showTopicTimerModal() {
const model = this.modelFor("topic");
const topicTimer = model.get("topic_timer");

View File

@ -114,7 +114,13 @@
<label for="topic-auto-close">
{{i18n "topic.auto_close.label"}}
</label>
{{text-field value=category.auto_close_hours id="topic-auto-close" type="number"}}
<div class="category-topic-auto-close-hours">
{{relative-time-picker
id="topic-auto-close"
durationHours=category.auto_close_hours
hiddenIntervals=hiddenRelativeIntervals
onChange=(action "onAutoCloseDurationChange")}}
</div>
<label>
{{input type="checkbox" checked=category.auto_close_based_on_last_post}}
{{i18n "topic.auto_close.based_on_last_post"}}

View File

@ -33,7 +33,7 @@
{{willCloseI18n}}
</div>
{{/if}}
{{#if showTopicStatusInfo}}
{{#if showTopicTimerInfo}}
<div class="alert alert-info modal-topic-timer-info">
{{topic-timer-info
statusType=statusType

View File

@ -1,5 +1,5 @@
<div class="relative-time-picker">
{{input class="relative-time-duration" min="0.1" step="0.01" type="number" value=duration onChange=(action "onChangeDuration")}}
{{input class="relative-time-duration" min=durationMin step=durationStep type="number" value=duration onChange=(action "onChangeDuration")}}
{{combo-box
content=intervals
value=selectedInterval

View File

@ -10,7 +10,7 @@
toggleClosed=toggleClosed
toggleArchived=toggleArchived
toggleVisibility=toggleVisibility
showTopicStatusUpdate=showTopicStatusUpdate
showTopicTimerModal=showTopicTimerModal
showFeatureTopic=showFeatureTopic
showChangeTimestamp=showChangeTimestamp
resetBumpDate=resetBumpDate

View File

@ -3,10 +3,25 @@
<span title={{title}}>
{{clockIcon}} {{notice}}
</span>
{{#if showTrashCan}}
<button type="button" class="btn topic-timer-remove no-text" title={{trashCanTitle}}>
{{trashCanIcon}}
</button>
{{/if}}
<div class="topic-timer-modify">
{{#if showTrashCan}}
{{d-button
ariaLabel="post.controls.remove_timer"
title="post.controls.remove_timer"
icon="trash-alt"
class="btn topic-timer-remove no-text"
action=removeTopicTimer
}}
{{/if}}
{{#if showEdit}}
{{d-button
ariaLabel="post.controls.edit_timer"
title="post.controls.edit_timer"
icon="pencil-alt"
class="btn topic-timer-edit no-text"
action=showTopicTimerModal
}}
{{/if}}
</div>
</h3>
{{/if}}

View File

@ -152,7 +152,7 @@
toggleClosed=(action "toggleClosed")
toggleArchived=(action "toggleArchived")
toggleVisibility=(action "toggleVisibility")
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
showTopicTimerModal=(route-action "showTopicTimerModal")
showFeatureTopic=(route-action "showFeatureTopic")
showChangeTimestamp=(route-action "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
@ -176,7 +176,7 @@
toggleClosed=(action "toggleClosed")
toggleArchived=(action "toggleArchived")
toggleVisibility=(action "toggleVisibility")
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
showTopicTimerModal=(route-action "showTopicTimerModal")
showFeatureTopic=(route-action "showFeatureTopic")
showChangeTimestamp=(route-action "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
@ -300,6 +300,7 @@
basedOnLastPost=model.topic_timer.based_on_last_post
durationMinutes=model.topic_timer.duration_minutes
categoryId=model.topic_timer.category_id
showTopicTimerModal=(route-action "showTopicTimerModal")
removeTopicTimer=(action "removeTopicTimer" model.topic_timer.status_type "topic_timer")}}
{{#if showSelectedPostsAtBottom}}
@ -345,7 +346,7 @@
toggleClosed=(action "toggleClosed")
toggleArchived=(action "toggleArchived")
toggleVisibility=(action "toggleVisibility")
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
showTopicTimerModal=(route-action "showTopicTimerModal")
showFeatureTopic=(route-action "showFeatureTopic")
showChangeTimestamp=(route-action "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")

View File

@ -200,9 +200,9 @@ export default createWidget("topic-admin-menu", {
if (this.get("currentUser.canManageTopic")) {
this.addActionButton({
className: "topic-admin-status-update",
className: "admin-topic-timer-update",
buttonClass: "popup-menu-btn",
action: "showTopicStatusUpdate",
action: "showTopicTimerModal",
icon: "far-clock",
label: "actions.timed_update",
});

View File

@ -139,7 +139,7 @@ acceptance("Modal Keyboard Events", function (needs) {
await visit("/t/internationalization-localization/280");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await triggerKeyEvent(".d-modal", "keyup", 13);
assert.ok(

View File

@ -28,12 +28,12 @@ acceptance("Topic - Edit timer", function (needs) {
updateCurrentUser({ moderator: true });
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await click("#tap_tile_next_week");
const regex = /will automatically close in/g;
const html = queryAll(".edit-topic-timer-modal .topic-status-info")
const html = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex.test(html));
@ -44,12 +44,12 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await click("#tap_tile_next_week");
const regex1 = /will automatically close in/g;
const html1 = queryAll(".edit-topic-timer-modal .topic-status-info")
const html1 = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex1.test(html1));
@ -58,7 +58,7 @@ acceptance("Topic - Edit timer", function (needs) {
await fillIn(".tap-tile-date-input .date-picker", "2099-11-24");
const regex2 = /will automatically close in/g;
const html2 = queryAll(".edit-topic-timer-modal .topic-status-info")
const html2 = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex2.test(html2));
@ -83,7 +83,7 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await timerType.expand();
await timerType.selectRowByValue("open");
@ -91,7 +91,7 @@ acceptance("Topic - Edit timer", function (needs) {
await click("#tap_tile_next_week");
const regex1 = /will automatically open in/g;
const html1 = queryAll(".edit-topic-timer-modal .topic-status-info")
const html1 = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex1.test(html1));
@ -100,7 +100,7 @@ acceptance("Topic - Edit timer", function (needs) {
await fillIn(".tap-tile-date-input .date-picker", "2099-11-24");
const regex2 = /will automatically open in/g;
const html2 = queryAll(".edit-topic-timer-modal .topic-status-info")
const html2 = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex2.test(html2));
@ -113,7 +113,7 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await timerType.expand();
await timerType.selectRowByValue("publish_to_category");
@ -127,7 +127,7 @@ acceptance("Topic - Edit timer", function (needs) {
await click("#tap_tile_next_week");
const regex = /will be published to #dev/g;
const text = queryAll(".edit-topic-timer-modal .topic-status-info")
const text = queryAll(".edit-topic-timer-modal .topic-timer-info")
.text()
.trim();
assert.ok(regex.test(text));
@ -138,7 +138,7 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
const timerType = selectKit(".select-kit.timer-type");
@ -153,7 +153,7 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await timerType.expand();
await timerType.selectRowByValue("delete");
@ -161,7 +161,7 @@ acceptance("Topic - Edit timer", function (needs) {
await click("#tap_tile_two_weeks");
const regex = /will be automatically deleted/g;
const html = queryAll(".edit-topic-timer-modal .topic-status-info")
const html = queryAll(".edit-topic-timer-modal .topic-timer-info")
.html()
.trim();
assert.ok(regex.test(html));
@ -172,17 +172,15 @@ acceptance("Topic - Edit timer", function (needs) {
await visit("/t/internationalization-localization");
await click(".toggle-admin-menu");
await click(".topic-admin-status-update button");
await click(".admin-topic-timer-update button");
await click("#tap_tile_next_week");
await click(".edit-topic-timer-buttons button.btn-primary");
const removeTimerButton = queryAll(
".topic-status-info .topic-timer-remove"
);
const removeTimerButton = queryAll(".topic-timer-info .topic-timer-remove");
assert.equal(removeTimerButton.attr("title"), "remove timer");
await click(".topic-status-info .topic-timer-remove");
const topicStatusInfo = queryAll(".topic-status-info .topic-timer-remove");
assert.equal(topicStatusInfo.length, 0);
await click(".topic-timer-info .topic-timer-remove");
const topicTimerInfo = queryAll(".topic-timer-info .topic-timer-remove");
assert.equal(topicTimerInfo.length, 0);
});
});

View File

@ -16,7 +16,7 @@ const createArgs = (topic) => {
toggleClosed: () => {},
toggleArchived: () => {},
toggleVisibility: () => {},
showTopicStatusUpdate: () => {},
showTopicTimerModal: () => {},
showFeatureTopic: () => {},
showChangeTimestamp: () => {},
resetBumpDate: () => {},

View File

@ -152,6 +152,10 @@ div.edit-category {
align-self: start;
padding: 0 1.5em 2em 0;
}
.category-topic-auto-close-hours {
width: 200px;
}
}
.category-permissions-table {

View File

@ -42,7 +42,7 @@
.btn-clear {
display: none;
}
.topic-status-info {
.topic-timer-info {
border: none;
padding: 0;
h3 {
@ -60,7 +60,7 @@
}
.modal-topic-timer-info {
.topic-status-info {
.topic-timer-info {
border-top: 0;
}
}

View File

@ -15,7 +15,7 @@
._flyout,
#topic-progress,
.quote-controls,
.topic-status-info,
.topic-timer-info,
div.lazyYT,
.post-info.edits,
.post-action,

View File

@ -59,7 +59,7 @@
}
}
.topic-status-info {
.topic-timer-info {
border-top: 1px solid var(--primary-low);
padding: 10px 0;
max-width: 758px;
@ -69,11 +69,14 @@
align-items: center;
margin: 0px;
}
.topic-timer-modify {
margin-left: auto;
}
.topic-timer-remove,
.slow-mode-remove {
.slow-mode-remove,
.topic-timer-edit {
font-size: $font-down-2;
background: transparent;
margin-left: auto;
}
}

View File

@ -28,7 +28,7 @@
}
}
.topic-status-info {
.topic-timer-info {
padding-left: 10px;
border-top: 1px solid var(--primary-low);
padding-top: 10px;

View File

@ -2475,7 +2475,7 @@ en:
title: "Close Temporarily"
auto_close:
title: "Auto-Close Topic"
label: "Auto-close topic hours:"
label: "Auto-close topic after:"
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."
auto_close_after_last_post:
@ -2947,6 +2947,7 @@ en:
change_post_notice: "Change Staff Notice"
delete_post_notice: "Delete Staff Notice"
remove_timer: "remove timer"
edit_timer: "edit timer"
actions:
people: