FEATURE: show user status on the user profile page (#17712)
This commit is contained in:
parent
391c687afb
commit
2cb97d8de4
|
@ -0,0 +1,26 @@
|
|||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class extends Component {
|
||||
tagName = "";
|
||||
|
||||
@computed("status.ends_at")
|
||||
get until() {
|
||||
if (!this.status.ends_at) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timezone = this.currentUser.timezone;
|
||||
const endsAt = moment.tz(this.status.ends_at, timezone);
|
||||
const now = moment.tz(timezone);
|
||||
const until = I18n.t("user_status.until");
|
||||
|
||||
if (now.isSame(endsAt, "day")) {
|
||||
const localeData = moment.localeData(this.currentUser.locale);
|
||||
return `${until} ${endsAt.format(localeData.longDateFormat("LT"))}`;
|
||||
} else {
|
||||
return `${until} ${endsAt.format("MMM D")}`;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ export default DiscourseRoute.extend({
|
|||
return user
|
||||
.findDetails()
|
||||
.then(() => user.findStaffInfo())
|
||||
.then(() => user.trackStatus())
|
||||
.catch(() => this.replaceWith("/404"));
|
||||
},
|
||||
|
||||
|
@ -87,6 +88,7 @@ export default DiscourseRoute.extend({
|
|||
const user = this.modelFor("user");
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}`);
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}/counters`);
|
||||
user.stopTrackingStatus();
|
||||
|
||||
// Remove the search context
|
||||
this.searchService.set("searchContext", null);
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<span class="user-status-message">
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
{{#if @showDescription}}
|
||||
<span class="user-status-message-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{/if}}
|
||||
<DTooltip>
|
||||
<div class="user-status-message-tooltip">
|
||||
{{emoji @status.emoji skipTitle=true}}
|
||||
<span class="user-status-tooltip-description">
|
||||
{{@status.description}}
|
||||
</span>
|
||||
{{#if this.until}}
|
||||
<div class="user-status-tooltip-until">
|
||||
{{this.until}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</DTooltip>
|
||||
</span>
|
|
@ -79,7 +79,13 @@
|
|||
|
||||
<div class="primary-textual">
|
||||
<div class="user-profile-names">
|
||||
<h1 class={{if this.nameFirst "full-name" "username"}}>{{if this.nameFirst this.model.name (format-username this.model.username)}} {{user-status this.model currentUser=this.currentUser}}</h1>
|
||||
<h1 class={{if this.nameFirst "full-name" "username"}}>
|
||||
{{if this.nameFirst this.model.name (format-username this.model.username)}}
|
||||
{{user-status this.model currentUser=this.currentUser}}
|
||||
{{#if this.model.status}}
|
||||
<UserStatusMessage @status={{this.model.status}} />
|
||||
{{/if}}
|
||||
</h1>
|
||||
<h2 class={{if this.nameFirst "username" "full-name"}}>{{#if this.nameFirst}}{{this.model.username}}{{else}}{{this.model.name}}{{/if}}</h2>
|
||||
{{#if this.model.staged}}
|
||||
<h2 class="staged">{{i18n "user.staged"}}</h2>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import I18n from "I18n";
|
||||
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
|
||||
acceptance("User Profile - Summary", function (needs) {
|
||||
needs.user();
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/u/eviltrout.json", () => {
|
||||
const response = cloneJSON(userFixtures["/u/eviltrout.json"]);
|
||||
return helper.response(response);
|
||||
});
|
||||
});
|
||||
|
||||
test("Viewing Summary", async function (assert) {
|
||||
await visit("/u/eviltrout/summary");
|
||||
|
||||
assert.ok(exists(".replies-section li a"), "replies");
|
||||
assert.ok(exists(".topics-section li a"), "topics");
|
||||
assert.ok(exists(".links-section li a"), "links");
|
||||
assert.ok(exists(".replied-section .user-info"), "liked by");
|
||||
assert.ok(exists(".liked-by-section .user-info"), "liked by");
|
||||
assert.ok(exists(".liked-section .user-info"), "liked");
|
||||
assert.ok(exists(".badges-section .badge-card"), "badges");
|
||||
assert.ok(
|
||||
exists(".top-categories-section .category-link"),
|
||||
"top categories"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Profile - Summary - User Status", function (needs) {
|
||||
needs.user();
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/u/eviltrout.json", () => {
|
||||
const response = cloneJSON(userFixtures["/u/eviltrout.json"]);
|
||||
response.user.status = {
|
||||
description: "off to dentist",
|
||||
emoji: "tooth",
|
||||
};
|
||||
return helper.response(response);
|
||||
});
|
||||
});
|
||||
|
||||
test("Shows User Status", async function (assert) {
|
||||
await visit("/u/eviltrout/summary");
|
||||
assert.ok(exists(".user-status-message .emoji[alt='tooth']"));
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Profile - Summary - Stats", function (needs) {
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/u/eviltrout/summary.json", () => {
|
||||
return helper.response(200, {
|
||||
user_summary: {
|
||||
likes_given: 1,
|
||||
likes_received: 2,
|
||||
topics_entered: 3,
|
||||
posts_read_count: 4,
|
||||
days_visited: 5,
|
||||
topic_count: 6,
|
||||
post_count: 7,
|
||||
time_read: 100000,
|
||||
recent_time_read: 1000,
|
||||
bookmark_count: 0,
|
||||
can_see_summary_stats: true,
|
||||
topic_ids: [1234],
|
||||
replies: [{ topic_id: 1234 }],
|
||||
links: [{ topic_id: 1234, url: "https://eviltrout.com" }],
|
||||
most_replied_to_users: [{ id: 333 }],
|
||||
most_liked_by_users: [{ id: 333 }],
|
||||
most_liked_users: [{ id: 333 }],
|
||||
badges: [{ badge_id: 444 }],
|
||||
top_categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "bug",
|
||||
color: "e9dd00",
|
||||
text_color: "000000",
|
||||
slug: "bug",
|
||||
read_restricted: false,
|
||||
parent_category_id: null,
|
||||
topic_count: 1,
|
||||
post_count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
badges: [{ id: 444, count: 1 }],
|
||||
topics: [{ id: 1234, title: "cool title", slug: "cool-title" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("Summary Read Times", async function (assert) {
|
||||
await visit("/u/eviltrout/summary");
|
||||
|
||||
assert.equal(query(".stats-time-read span").textContent.trim(), "1d");
|
||||
assert.equal(
|
||||
query(".stats-time-read span").title,
|
||||
I18n.t("user.summary.time_read_title", { duration: "1 day" })
|
||||
);
|
||||
|
||||
assert.equal(query(".stats-recent-read span").textContent.trim(), "17m");
|
||||
assert.equal(
|
||||
query(".stats-recent-read span").title,
|
||||
I18n.t("user.summary.recent_time_read_title", { duration: "17 mins" })
|
||||
);
|
||||
});
|
||||
});
|
|
@ -13,7 +13,6 @@ import {
|
|||
import { click, currentRouteName, visit } from "@ember/test-helpers";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import { test } from "qunit";
|
||||
import I18n from "I18n";
|
||||
|
||||
acceptance("User Routes", function (needs) {
|
||||
needs.user();
|
||||
|
@ -93,22 +92,6 @@ acceptance("User Routes", function (needs) {
|
|||
assert.ok(exists(".container.viewing-self"), "has the viewing-self class");
|
||||
});
|
||||
|
||||
test("Viewing Summary", async function (assert) {
|
||||
await visit("/u/eviltrout/summary");
|
||||
|
||||
assert.ok(exists(".replies-section li a"), "replies");
|
||||
assert.ok(exists(".topics-section li a"), "topics");
|
||||
assert.ok(exists(".links-section li a"), "links");
|
||||
assert.ok(exists(".replied-section .user-info"), "liked by");
|
||||
assert.ok(exists(".liked-by-section .user-info"), "liked by");
|
||||
assert.ok(exists(".liked-section .user-info"), "liked");
|
||||
assert.ok(exists(".badges-section .badge-card"), "badges");
|
||||
assert.ok(
|
||||
exists(".top-categories-section .category-link"),
|
||||
"top categories"
|
||||
);
|
||||
});
|
||||
|
||||
test("Viewing Drafts", async function (assert) {
|
||||
await visit("/u/eviltrout/activity/drafts");
|
||||
assert.ok(exists(".user-stream"), "has drafts stream");
|
||||
|
@ -125,66 +108,6 @@ acceptance("User Routes", function (needs) {
|
|||
});
|
||||
});
|
||||
|
||||
acceptance("User Summary - Stats", function (needs) {
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/u/eviltrout/summary.json", () => {
|
||||
return helper.response(200, {
|
||||
user_summary: {
|
||||
likes_given: 1,
|
||||
likes_received: 2,
|
||||
topics_entered: 3,
|
||||
posts_read_count: 4,
|
||||
days_visited: 5,
|
||||
topic_count: 6,
|
||||
post_count: 7,
|
||||
time_read: 100000,
|
||||
recent_time_read: 1000,
|
||||
bookmark_count: 0,
|
||||
can_see_summary_stats: true,
|
||||
topic_ids: [1234],
|
||||
replies: [{ topic_id: 1234 }],
|
||||
links: [{ topic_id: 1234, url: "https://eviltrout.com" }],
|
||||
most_replied_to_users: [{ id: 333 }],
|
||||
most_liked_by_users: [{ id: 333 }],
|
||||
most_liked_users: [{ id: 333 }],
|
||||
badges: [{ badge_id: 444 }],
|
||||
top_categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "bug",
|
||||
color: "e9dd00",
|
||||
text_color: "000000",
|
||||
slug: "bug",
|
||||
read_restricted: false,
|
||||
parent_category_id: null,
|
||||
topic_count: 1,
|
||||
post_count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
badges: [{ id: 444, count: 1 }],
|
||||
topics: [{ id: 1234, title: "cool title", slug: "cool-title" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("Summary Read Times", async function (assert) {
|
||||
await visit("/u/eviltrout/summary");
|
||||
|
||||
assert.equal(query(".stats-time-read span").textContent.trim(), "1d");
|
||||
assert.equal(
|
||||
query(".stats-time-read span").title,
|
||||
I18n.t("user.summary.time_read_title", { duration: "1 day" })
|
||||
);
|
||||
|
||||
assert.equal(query(".stats-recent-read span").textContent.trim(), "17m");
|
||||
assert.equal(
|
||||
query(".stats-recent-read span").title,
|
||||
I18n.t("user.summary.recent_time_read_title", { duration: "17 mins" })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance(
|
||||
"User Routes - Periods in current user's username",
|
||||
function (needs) {
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import { module, test } from "qunit";
|
||||
import { render, triggerEvent } from "@ember/test-helpers";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { exists, fakeTime, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
async function mouseenter() {
|
||||
await triggerEvent(query(".user-status-message"), "mouseenter");
|
||||
}
|
||||
|
||||
module("Integration | Component | user-status-message", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.currentUser.timezone = "UTC";
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
if (this.clock) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("it renders user status emoji", async function (assert) {
|
||||
this.set("status", { emoji: "tooth", description: "off to dentist" });
|
||||
await render(hbs`<UserStatusMessage @status={{this.status}} />`);
|
||||
assert.ok(exists("img.emoji[alt='tooth']"), "the status emoji is shown");
|
||||
});
|
||||
|
||||
test("it doesn't render status description by default", async function (assert) {
|
||||
this.set("status", { emoji: "tooth", description: "off to dentist" });
|
||||
await render(hbs`<UserStatusMessage @status={{this.status}} />`);
|
||||
assert.notOk(exists(".user-status-message-description"));
|
||||
});
|
||||
|
||||
test("it renders status description if enabled", async function (assert) {
|
||||
this.set("status", { emoji: "tooth", description: "off to dentist" });
|
||||
await render(hbs`
|
||||
<UserStatusMessage
|
||||
@status={{this.status}}
|
||||
@showDescription=true/>
|
||||
`);
|
||||
assert.equal(
|
||||
query(".user-status-message-description").innerText.trim(),
|
||||
"off to dentist"
|
||||
);
|
||||
});
|
||||
|
||||
test("it shows the until TIME on the tooltip if status will expire today", async function (assert) {
|
||||
this.clock = fakeTime(
|
||||
"2100-02-01T08:00:00.000Z",
|
||||
this.currentUser.timezone,
|
||||
true
|
||||
);
|
||||
this.set("status", {
|
||||
emoji: "tooth",
|
||||
description: "off to dentist",
|
||||
ends_at: "2100-02-01T12:30:00.000Z",
|
||||
});
|
||||
|
||||
await render(hbs`<UserStatusMessage @status={{this.status}} />`);
|
||||
|
||||
await mouseenter();
|
||||
assert.equal(
|
||||
document
|
||||
.querySelector("[data-tippy-root] .user-status-tooltip-until")
|
||||
.textContent.trim(),
|
||||
"Until: 12:30 PM"
|
||||
);
|
||||
});
|
||||
|
||||
test("it shows the until DATE on the tooltip if status will expire tomorrow", async function (assert) {
|
||||
this.clock = fakeTime(
|
||||
"2100-02-01T08:00:00.000Z",
|
||||
this.currentUser.timezone,
|
||||
true
|
||||
);
|
||||
this.set("status", {
|
||||
emoji: "tooth",
|
||||
description: "off to dentist",
|
||||
ends_at: "2100-02-02T12:30:00.000Z",
|
||||
});
|
||||
|
||||
await render(hbs`<UserStatusMessage @status={{this.status}} />`);
|
||||
|
||||
await mouseenter();
|
||||
assert.equal(
|
||||
document
|
||||
.querySelector("[data-tippy-root] .user-status-tooltip-until")
|
||||
.textContent.trim(),
|
||||
"Until: Feb 2"
|
||||
);
|
||||
});
|
||||
|
||||
test("it doesn't show until datetime on the tooltip if status doesn't have expiration date", async function (assert) {
|
||||
this.clock = fakeTime(
|
||||
"2100-02-01T08:00:00.000Z",
|
||||
this.currentUser.timezone,
|
||||
true
|
||||
);
|
||||
this.set("status", {
|
||||
emoji: "tooth",
|
||||
description: "off to dentist",
|
||||
ends_at: null,
|
||||
});
|
||||
|
||||
await render(hbs`<UserStatusMessage @status={{this.status}} />`);
|
||||
|
||||
await mouseenter();
|
||||
assert.notOk(
|
||||
document.querySelector("[data-tippy-root] .user-status-tooltip-until")
|
||||
);
|
||||
});
|
||||
});
|
|
@ -29,6 +29,7 @@
|
|||
@import "time-shortcut-picker";
|
||||
@import "user-card";
|
||||
@import "user-info";
|
||||
@import "user-status-message";
|
||||
@import "user-status-picker";
|
||||
@import "user-stream-item";
|
||||
@import "user-stream";
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
.user-status-message-tooltip {
|
||||
.emoji {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.user-status-tooltip-description {
|
||||
font-weight: bold;
|
||||
margin-left: 0.1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.user-status-tooltip-until {
|
||||
margin-top: 0.2em;
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue