FEATURE: user status emoji (#17025)
This commit is contained in:
parent
0b8e6adabe
commit
033f72c65f
|
@ -14,6 +14,7 @@ import { isEmpty } from "@ember/utils";
|
||||||
import { prioritizeNameInUx } from "discourse/lib/settings";
|
import { prioritizeNameInUx } from "discourse/lib/settings";
|
||||||
import { dasherize } from "@ember/string";
|
import { dasherize } from "@ember/string";
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
|
|
||||||
export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
|
export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
|
||||||
elementId: "user-card",
|
elementId: "user-card",
|
||||||
|
@ -55,10 +56,9 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
|
||||||
return this.siteSettings.enable_user_status && this.user.status;
|
return this.siteSettings.enable_user_status && this.user.status;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("user.status")
|
@discourseComputed("user.status.emoji")
|
||||||
userStatusEmoji() {
|
userStatusEmoji(emoji) {
|
||||||
const emoji = this.user.status.emoji ?? "mega";
|
return emojiUnescape(escapeExpression(`:${emoji}:`));
|
||||||
return emojiUnescape(`:${emoji}:`);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isSuspendedOrHasBio: or("user.suspend_reason", "user.bio_excerpt"),
|
isSuspendedOrHasBio: or("user.suspend_reason", "user.bio_excerpt"),
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { action, computed } from "@ember/object";
|
||||||
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
|
|
||||||
|
export default class UserStatusPicker extends Component {
|
||||||
|
tagName = "";
|
||||||
|
isFocused = false;
|
||||||
|
emojiPickerIsActive = false;
|
||||||
|
emoji = null;
|
||||||
|
description = null;
|
||||||
|
|
||||||
|
@computed("emoji")
|
||||||
|
get emojiHtml() {
|
||||||
|
const emoji = escapeExpression(`:${this.emoji}:`);
|
||||||
|
return emojiUnescape(emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
blur() {
|
||||||
|
this.set("isFocused", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
emojiSelected(emoji) {
|
||||||
|
this.set("emoji", emoji);
|
||||||
|
this.set("emojiPickerIsActive", false);
|
||||||
|
|
||||||
|
scheduleOnce("afterRender", () => {
|
||||||
|
document.querySelector(".btn-emoji")?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
focus() {
|
||||||
|
this.set("isFocused", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onEmojiPickerOutsideClick() {
|
||||||
|
this.set("emojiPickerIsActive", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setDefaultEmoji() {
|
||||||
|
if (!this.emoji) {
|
||||||
|
this.set("emoji", "speech_balloon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleEmojiPicker(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.set("emojiPickerIsActive", !this.emojiPickerIsActive);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +1,49 @@
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { notEmpty } from "@ember/object/computed";
|
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import bootbox from "bootbox";
|
import bootbox from "bootbox";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default Controller.extend(ModalFunctionality, {
|
export default Controller.extend(ModalFunctionality, {
|
||||||
userStatusService: service("user-status"),
|
userStatusService: service("user-status"),
|
||||||
|
|
||||||
|
emoji: null,
|
||||||
description: null,
|
description: null,
|
||||||
statusIsSet: notEmpty("description"),
|
|
||||||
showDeleteButton: false,
|
showDeleteButton: false,
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
if (this.currentUser.status) {
|
const status = this.currentUser.status;
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
description: this.currentUser.status.description,
|
emoji: status?.emoji,
|
||||||
showDeleteButton: true,
|
description: status?.description,
|
||||||
|
showDeleteButton: !!status,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
@discourseComputed("emoji", "description")
|
||||||
|
statusIsSet(emoji, description) {
|
||||||
|
return !!emoji && !!description;
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
delete() {
|
delete() {
|
||||||
this.userStatusService
|
this.userStatusService
|
||||||
.clear()
|
.clear()
|
||||||
.then(() => {
|
.then(() => this.send("closeModal"))
|
||||||
this._resetModal();
|
|
||||||
this.send("closeModal");
|
|
||||||
})
|
|
||||||
.catch((e) => this._handleError(e));
|
.catch((e) => this._handleError(e));
|
||||||
},
|
},
|
||||||
|
|
||||||
@action
|
@action
|
||||||
saveAndClose() {
|
saveAndClose() {
|
||||||
if (this.description) {
|
const status = { description: this.description, emoji: this.emoji };
|
||||||
const status = { description: this.description };
|
|
||||||
this.userStatusService
|
this.userStatusService
|
||||||
.set(status)
|
.set(status)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
})
|
})
|
||||||
.catch((e) => this._handleError(e));
|
.catch((e) => this._handleError(e));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleError(e) {
|
_handleError(e) {
|
||||||
|
@ -53,9 +53,4 @@ export default Controller.extend(ModalFunctionality, {
|
||||||
popupAjaxError(e);
|
popupAjaxError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_resetModal() {
|
|
||||||
this.set("description", null);
|
|
||||||
this.set("showDeleteButton", false);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default class UserStatusService extends Service {
|
||||||
await ajax({
|
await ajax({
|
||||||
url: "/user-status.json",
|
url: "/user-status.json",
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: { description: status.description },
|
data: status,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentUser.set("status", status);
|
this.currentUser.set("status", status);
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="user-status-picker-wrap">
|
||||||
|
<div class="emoji-picker-anchor user-status-picker {{if this.isFocused "focused"}}">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-emoji btn-flat"
|
||||||
|
onclick={{action "toggleEmojiPicker"}}
|
||||||
|
{{on "focus" this.focus}}
|
||||||
|
{{on "blur" this.blur}}
|
||||||
|
>
|
||||||
|
{{#if @emoji}}
|
||||||
|
{{html-safe this.emojiHtml}}
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "discourse-emojis"}}
|
||||||
|
{{/if}}
|
||||||
|
</button>
|
||||||
|
<Input
|
||||||
|
class="user-status-description"
|
||||||
|
@value={{@description}}
|
||||||
|
placeholder={{i18n "user_status.what_are_you_doing"}}
|
||||||
|
{{on "input" this.setDefaultEmoji}}
|
||||||
|
{{on "focus" this.focus}}
|
||||||
|
{{on "blur" this.blur}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{emoji-picker
|
||||||
|
isActive=this.emojiPickerIsActive
|
||||||
|
emojiSelected=(action "emojiSelected")
|
||||||
|
onEmojiPickerClose=(action "onEmojiPickerOutsideClick")
|
||||||
|
placement="bottom"
|
||||||
|
}}
|
|
@ -1,10 +1,7 @@
|
||||||
{{#d-modal-body}}
|
{{#d-modal-body}}
|
||||||
{{#conditional-loading-spinner condition=loading}}
|
{{#conditional-loading-spinner condition=loading}}
|
||||||
<div class="control-group user-status-description-wrap">
|
<div class="control-group">
|
||||||
{{input
|
{{user-status-picker emoji=emoji description=description}}
|
||||||
class="user-status-description"
|
|
||||||
placeholder=(i18n "user_status.what_are_you_doing")
|
|
||||||
value=description}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer control-group">
|
<div class="modal-footer control-group">
|
||||||
{{d-button
|
{{d-button
|
||||||
|
|
|
@ -30,10 +30,9 @@ createWidgetFrom(QuickAccessItem, "user-status-item", {
|
||||||
const action = "hideMenuAndSetStatus";
|
const action = "hideMenuAndSetStatus";
|
||||||
const userStatus = this.currentUser.status;
|
const userStatus = this.currentUser.status;
|
||||||
if (userStatus) {
|
if (userStatus) {
|
||||||
const emoji = userStatus.emoji ?? "mega";
|
|
||||||
return this.attach("flat-button", {
|
return this.attach("flat-button", {
|
||||||
action,
|
action,
|
||||||
emoji,
|
emoji: userStatus.emoji,
|
||||||
translatedLabel: userStatus.description,
|
translatedLabel: userStatus.description,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,6 @@ export default createWidget("user-status-bubble", {
|
||||||
tagName: "div.user-status-background",
|
tagName: "div.user-status-background",
|
||||||
|
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
const emoji = attrs.emoji ?? "mega";
|
return this.attach("emoji", { name: attrs.emoji });
|
||||||
return this.attach("emoji", { name: emoji });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,9 +8,21 @@ import {
|
||||||
import { click, fillIn, visit } from "@ember/test-helpers";
|
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
|
|
||||||
|
async function openUserStatusModal() {
|
||||||
|
await click(".header-dropdown-toggle.current-user");
|
||||||
|
await click(".menu-links-row .user-preferences-link");
|
||||||
|
await click(".user-status button");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickEmoji(emoji) {
|
||||||
|
await click(".btn-emoji");
|
||||||
|
await fillIn(".emoji-picker-content .filter", emoji);
|
||||||
|
await click(".results .emoji");
|
||||||
|
}
|
||||||
|
|
||||||
acceptance("User Status", function (needs) {
|
acceptance("User Status", function (needs) {
|
||||||
const userStatusFallbackEmoji = "mega";
|
|
||||||
const userStatus = "off to dentist";
|
const userStatus = "off to dentist";
|
||||||
|
const userStatusEmoji = "tooth";
|
||||||
const userId = 1;
|
const userId = 1;
|
||||||
|
|
||||||
needs.user({ id: userId });
|
needs.user({ id: userId });
|
||||||
|
@ -19,6 +31,7 @@ acceptance("User Status", function (needs) {
|
||||||
server.put("/user-status.json", () => {
|
server.put("/user-status.json", () => {
|
||||||
publishToMessageBus(`/user-status/${userId}`, {
|
publishToMessageBus(`/user-status/${userId}`, {
|
||||||
description: userStatus,
|
description: userStatus,
|
||||||
|
emoji: userStatusEmoji,
|
||||||
});
|
});
|
||||||
return helper.response({ success: true });
|
return helper.response({ success: true });
|
||||||
});
|
});
|
||||||
|
@ -38,7 +51,7 @@ acceptance("User Status", function (needs) {
|
||||||
assert.notOk(exists("div.quick-access-panel li.user-status"));
|
assert.notOk(exists("div.quick-access-panel li.user-status"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows the user status button on the menu when disabled in settings", async function (assert) {
|
test("shows the user status button on the menu when enabled in settings", async function (assert) {
|
||||||
this.siteSettings.enable_user_status = true;
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
@ -57,7 +70,9 @@ acceptance("User Status", function (needs) {
|
||||||
|
|
||||||
test("shows user status on loaded page", async function (assert) {
|
test("shows user status on loaded page", async function (assert) {
|
||||||
this.siteSettings.enable_user_status = true;
|
this.siteSettings.enable_user_status = true;
|
||||||
updateCurrentUser({ status: { description: userStatus } });
|
updateCurrentUser({
|
||||||
|
status: { description: userStatus, emoji: userStatusEmoji },
|
||||||
|
});
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await click(".header-dropdown-toggle.current-user");
|
||||||
|
@ -72,30 +87,75 @@ acceptance("User Status", function (needs) {
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query("div.quick-access-panel li.user-status img.emoji").alt,
|
query("div.quick-access-panel li.user-status img.emoji").alt,
|
||||||
`:${userStatusFallbackEmoji}:`,
|
`:${userStatusEmoji}:`,
|
||||||
"shows user status emoji on the menu"
|
"shows user status emoji on the menu"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
|
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
|
||||||
`:${userStatusFallbackEmoji}:`,
|
`:${userStatusEmoji}:`,
|
||||||
"shows user status emoji on the user avatar in the header"
|
"shows user status emoji on the user avatar in the header"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("shows user status on the user status modal", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
updateCurrentUser({
|
||||||
|
status: { description: userStatus, emoji: userStatusEmoji },
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
query(`.btn-emoji img.emoji`).title,
|
||||||
|
userStatusEmoji,
|
||||||
|
"status emoji is shown"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
query(".user-status-description").value,
|
||||||
|
userStatus,
|
||||||
|
"status description is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("emoji picking", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
|
||||||
|
assert.ok(exists(`.d-icon-discourse-emojis`), "empty status icon is shown");
|
||||||
|
|
||||||
|
await click(".btn-emoji");
|
||||||
|
assert.ok(exists(".emoji-picker.opened"), "emoji picker is opened");
|
||||||
|
|
||||||
|
await fillIn(".emoji-picker-content .filter", userStatusEmoji);
|
||||||
|
await click(".results .emoji");
|
||||||
|
assert.ok(
|
||||||
|
exists(`.btn-emoji img.emoji[title=${userStatusEmoji}]`),
|
||||||
|
"chosen status emoji is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("setting user status", async function (assert) {
|
test("setting user status", async function (assert) {
|
||||||
this.siteSettings.enable_user_status = true;
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await openUserStatusModal();
|
||||||
await click(".menu-links-row .user-preferences-link");
|
|
||||||
await click(".user-status button");
|
|
||||||
await fillIn(".user-status-description", userStatus);
|
await fillIn(".user-status-description", userStatus);
|
||||||
await click(".btn-primary");
|
await pickEmoji(userStatusEmoji);
|
||||||
|
assert.ok(
|
||||||
|
exists(`.btn-emoji img.emoji[title=${userStatusEmoji}]`),
|
||||||
|
"chosen status emoji is shown"
|
||||||
|
);
|
||||||
|
await click(".btn-primary"); // save
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
|
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
|
||||||
`:${userStatusFallbackEmoji}:`,
|
`:${userStatusEmoji}:`,
|
||||||
"shows user status emoji on the user avatar in the header"
|
"shows user status emoji on the user avatar in the header"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -110,7 +170,7 @@ acceptance("User Status", function (needs) {
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
query("div.quick-access-panel li.user-status img.emoji").alt,
|
query("div.quick-access-panel li.user-status img.emoji").alt,
|
||||||
`:${userStatusFallbackEmoji}:`,
|
`:${userStatusEmoji}:`,
|
||||||
"shows user status emoji on the menu"
|
"shows user status emoji on the menu"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -121,11 +181,11 @@ acceptance("User Status", function (needs) {
|
||||||
const updatedStatus = "off to dentist the second time";
|
const updatedStatus = "off to dentist the second time";
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await openUserStatusModal();
|
||||||
await click(".menu-links-row .user-preferences-link");
|
|
||||||
await click(".user-status button");
|
|
||||||
await fillIn(".user-status-description", updatedStatus);
|
await fillIn(".user-status-description", updatedStatus);
|
||||||
await click(".btn-primary");
|
await pickEmoji(userStatusEmoji);
|
||||||
|
await click(".btn-primary"); // save
|
||||||
|
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await click(".header-dropdown-toggle.current-user");
|
||||||
await click(".menu-links-row .user-preferences-link");
|
await click(".menu-links-row .user-preferences-link");
|
||||||
|
@ -135,6 +195,11 @@ acceptance("User Status", function (needs) {
|
||||||
updatedStatus,
|
updatedStatus,
|
||||||
"shows user status description on the menu"
|
"shows user status description on the menu"
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
query("div.quick-access-panel li.user-status img.emoji").alt,
|
||||||
|
`:${userStatusEmoji}:`,
|
||||||
|
"shows user status emoji on the menu"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clearing user status", async function (assert) {
|
test("clearing user status", async function (assert) {
|
||||||
|
@ -142,22 +207,68 @@ acceptance("User Status", function (needs) {
|
||||||
updateCurrentUser({ status: { description: userStatus } });
|
updateCurrentUser({ status: { description: userStatus } });
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await openUserStatusModal();
|
||||||
await click(".menu-links-row .user-preferences-link");
|
|
||||||
await click(".user-status button");
|
|
||||||
await click(".btn.delete-status");
|
await click(".btn.delete-status");
|
||||||
|
|
||||||
assert.notOk(exists(".header-dropdown-toggle .user-status-background"));
|
assert.notOk(exists(".header-dropdown-toggle .user-status-background"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("it's impossible to set status without description", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
await pickEmoji(userStatusEmoji);
|
||||||
|
|
||||||
|
assert.ok(exists(`.btn-primary[disabled]`), "the save button is disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sets default status emoji automatically after user started inputting status description", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
const defaultStatusEmoji = "speech_balloon";
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
await fillIn(".user-status-description", "some status");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(`.btn-emoji img.emoji[title=${defaultStatusEmoji}]`),
|
||||||
|
"default status emoji is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows actual status on the modal after canceling the modal", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
updateCurrentUser({
|
||||||
|
status: { description: userStatus, emoji: userStatusEmoji },
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
await fillIn(".user-status-description", "another status");
|
||||||
|
await pickEmoji("cold_face"); // another emoji
|
||||||
|
await click(".d-modal-cancel");
|
||||||
|
await openUserStatusModal();
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
query(`.btn-emoji img.emoji`).title,
|
||||||
|
userStatusEmoji,
|
||||||
|
"the actual status emoji is shown"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
query(".user-status-description").value,
|
||||||
|
userStatus,
|
||||||
|
"the actual status description is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("shows the trash button when editing status that was set before", async function (assert) {
|
test("shows the trash button when editing status that was set before", async function (assert) {
|
||||||
this.siteSettings.enable_user_status = true;
|
this.siteSettings.enable_user_status = true;
|
||||||
updateCurrentUser({ status: { description: userStatus } });
|
updateCurrentUser({ status: { description: userStatus } });
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await openUserStatusModal();
|
||||||
await click(".menu-links-row .user-preferences-link");
|
|
||||||
await click(".user-status button");
|
|
||||||
|
|
||||||
assert.ok(exists(".btn.delete-status"));
|
assert.ok(exists(".btn.delete-status"));
|
||||||
});
|
});
|
||||||
|
@ -167,10 +278,28 @@ acceptance("User Status", function (needs) {
|
||||||
updateCurrentUser({ status: null });
|
updateCurrentUser({ status: null });
|
||||||
|
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click(".header-dropdown-toggle.current-user");
|
await openUserStatusModal();
|
||||||
await click(".menu-links-row .user-preferences-link");
|
|
||||||
await click(".user-status button");
|
|
||||||
|
|
||||||
assert.notOk(exists(".btn.delete-status"));
|
assert.notOk(exists(".btn.delete-status"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("shows empty modal after deleting the status", async function (assert) {
|
||||||
|
this.siteSettings.enable_user_status = true;
|
||||||
|
|
||||||
|
updateCurrentUser({
|
||||||
|
status: { description: userStatus, emoji: userStatusEmoji },
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await openUserStatusModal();
|
||||||
|
await click(".btn.delete-status");
|
||||||
|
await openUserStatusModal();
|
||||||
|
|
||||||
|
assert.ok(exists(`.d-icon-discourse-emojis`), "empty status icon is shown");
|
||||||
|
assert.equal(
|
||||||
|
query(".user-status-description").value,
|
||||||
|
"",
|
||||||
|
"no status description is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
@import "time-shortcut-picker";
|
@import "time-shortcut-picker";
|
||||||
@import "user-card";
|
@import "user-card";
|
||||||
@import "user-info";
|
@import "user-info";
|
||||||
|
@import "user-status-picker";
|
||||||
@import "user-stream-item";
|
@import "user-stream-item";
|
||||||
@import "user-stream";
|
@import "user-stream";
|
||||||
@import "widget-dropdown";
|
@import "widget-dropdown";
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
.user-status-picker-wrap {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: end;
|
||||||
|
|
||||||
|
.user-status-picker {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--primary-medium);
|
||||||
|
|
||||||
|
.btn-emoji {
|
||||||
|
margin: 3px;
|
||||||
|
width: 2.3em;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status-description {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status-picker.focused {
|
||||||
|
border: 1px solid var(--tertiary);
|
||||||
|
outline: 1px solid var(--tertiary);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,17 +22,5 @@
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ember-text-field.user-status-description {
|
|
||||||
min-width: 220px;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-status-description-wrap {
|
|
||||||
display: inline-flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ class UserStatusController < ApplicationController
|
||||||
|
|
||||||
def set
|
def set
|
||||||
ensure_feature_enabled
|
ensure_feature_enabled
|
||||||
raise Discourse::InvalidParameters.new(:description) if params[:description].blank?
|
description = params.require(:description)
|
||||||
|
emoji = params.require(:emoji)
|
||||||
|
|
||||||
current_user.set_status!(params[:description])
|
current_user.set_status!(description, emoji)
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1522,14 +1522,18 @@ class User < ActiveRecord::Base
|
||||||
publish_user_status(nil)
|
publish_user_status(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status!(description)
|
def set_status!(description, emoji)
|
||||||
now = Time.zone.now
|
now = Time.zone.now
|
||||||
if user_status
|
if user_status
|
||||||
user_status.update!(description: description, set_at: now)
|
user_status.update!(
|
||||||
|
description: description,
|
||||||
|
emoji: emoji,
|
||||||
|
set_at: now)
|
||||||
else
|
else
|
||||||
self.user_status = UserStatus.create!(
|
self.user_status = UserStatus.create!(
|
||||||
user_id: id,
|
user_id: id,
|
||||||
description: description,
|
description: description,
|
||||||
|
emoji: emoji,
|
||||||
set_at: now
|
set_at: now
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ end
|
||||||
# Table name: user_statuses
|
# Table name: user_statuses
|
||||||
#
|
#
|
||||||
# user_id :integer not null, primary key
|
# user_id :integer not null, primary key
|
||||||
# emoji :string
|
# emoji :string not null
|
||||||
# description :string not null
|
# description :string not null
|
||||||
# set_at :datetime not null
|
# set_at :datetime not null
|
||||||
# ends_at :datetime
|
# ends_at :datetime
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DisallowNullEmojiOnUserStatus < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
execute "UPDATE user_statuses SET emoji = 'speech_balloon'"
|
||||||
|
change_column :user_statuses, :emoji, :string, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
change_column :user_statuses, :emoji, :string, null: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,32 +25,46 @@ describe UserStatusController do
|
||||||
SiteSetting.enable_user_status = true
|
SiteSetting.enable_user_status = true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets user status" do
|
it 'the description parameter is mandatory' do
|
||||||
status = "off to dentist"
|
put "/user-status.json", params: { emoji: "tooth" }
|
||||||
put "/user-status.json", params: { description: status }
|
expect(response.status).to eq(400)
|
||||||
expect(user.user_status.description).to eq(status)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'the description parameter is mandatory' do
|
it 'the emoji parameter is mandatory' do
|
||||||
put "/user-status.json", params: {}
|
put "/user-status.json", params: { description: "off to dentist" }
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "sets user status" do
|
||||||
|
status = "off to dentist"
|
||||||
|
status_emoji = "tooth"
|
||||||
|
put "/user-status.json", params: { description: status, emoji: status_emoji }
|
||||||
|
expect(user.user_status.description).to eq(status)
|
||||||
|
expect(user.user_status.emoji).to eq(status_emoji)
|
||||||
|
end
|
||||||
|
|
||||||
it "following calls update status" do
|
it "following calls update status" do
|
||||||
status = "off to dentist"
|
status = "off to dentist"
|
||||||
put "/user-status.json", params: { description: status }
|
status_emoji = "tooth"
|
||||||
|
put "/user-status.json", params: { description: status, emoji: status_emoji }
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.user_status.description).to eq(status)
|
expect(user.user_status.description).to eq(status)
|
||||||
|
expect(user.user_status.emoji).to eq(status_emoji)
|
||||||
|
|
||||||
new_status = "working"
|
new_status = "surfing"
|
||||||
put "/user-status.json", params: { description: new_status }
|
new_status_emoji = "surfing_man"
|
||||||
|
put "/user-status.json", params: { description: new_status, emoji: new_status_emoji }
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.user_status.description).to eq(new_status)
|
expect(user.user_status.description).to eq(new_status)
|
||||||
|
expect(user.user_status.emoji).to eq(new_status_emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "publishes to message bus" do
|
it "publishes to message bus" do
|
||||||
status = "off to dentist"
|
status = "off to dentist"
|
||||||
messages = MessageBus.track_publish { put "/user-status.json", params: { description: status } }
|
emoji = "tooth"
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
put "/user-status.json", params: { description: status, emoji: emoji }
|
||||||
|
end
|
||||||
|
|
||||||
expect(messages.size).to eq(1)
|
expect(messages.size).to eq(1)
|
||||||
expect(messages[0].channel).to eq("/user-status/#{user.id}")
|
expect(messages[0].channel).to eq("/user-status/#{user.id}")
|
||||||
|
|
Loading…
Reference in New Issue