DEV: Add profile tab to the experimental user menu (#17982)
This commit adds the profile tab to the experimental user menu. We're adding it to the user menu because it contains links/buttons that are not available anywhere else. We may remove the tab again if we find better places for those links/buttons, but for now it'll stay. For more context on the experimental user menu, see https://github.com/discourse/discourse/pull/17379.
This commit is contained in:
parent
66376a6569
commit
67bb0d8a55
|
@ -11,37 +11,24 @@
|
||||||
<div class="menu-tabs-container" role="tablist" aria-orientation="vertical" aria-label={{i18n "user_menu.sr_menu_tabs"}}>
|
<div class="menu-tabs-container" role="tablist" aria-orientation="vertical" aria-label={{i18n "user_menu.sr_menu_tabs"}}>
|
||||||
<div class="top-tabs tabs-list">
|
<div class="top-tabs tabs-list">
|
||||||
{{#each this.topTabs as |tab|}}
|
{{#each this.topTabs as |tab|}}
|
||||||
<button
|
<UserMenu::TabButton
|
||||||
class={{concat "btn btn-flat btn-icon no-text" (if (eq tab.id this.currentTabId) " active")}}
|
@tab={{tab}}
|
||||||
type="button"
|
@currentTabId={{this.currentTabId}}
|
||||||
role="tab"
|
@changeTabFunction={{fn this.changeTab tab}}
|
||||||
id={{concat "user-menu-button-" tab.id}}
|
|
||||||
tabindex={{if (eq tab.id this.currentTabId) "0" "-1"}}
|
|
||||||
aria-selected={{if (eq tab.id this.currentTabId) "true" "false"}}
|
|
||||||
aria-controls={{concat "quick-access-" tab.id}}
|
|
||||||
data-tab-number={{tab.position}}
|
|
||||||
{{on "click" (fn this.changeTab tab)}}
|
|
||||||
{{!-- template-lint-disable require-context-role --}}
|
|
||||||
>
|
>
|
||||||
{{d-icon tab.icon}}
|
|
||||||
{{#if tab.count}}
|
{{#if tab.count}}
|
||||||
<span class="badge-notification">{{tab.count}}</span>
|
<span class="badge-notification">{{tab.count}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</button>
|
</UserMenu::TabButton>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-tabs tabs-list">
|
<div class="bottom-tabs tabs-list">
|
||||||
{{#each this.bottomTabs as |tab|}}
|
{{#each this.bottomTabs as |tab|}}
|
||||||
<a
|
<UserMenu::TabButton
|
||||||
class="btn btn-flat btn-icon no-text"
|
@tab={{tab}}
|
||||||
role="tab"
|
@currentTabId={{this.currentTabId}}
|
||||||
tabindex="-1"
|
@changeTabFunction={{fn this.changeTab tab}}
|
||||||
href={{tab.href}}
|
/>
|
||||||
data-tab-number={{tab.position}}
|
|
||||||
{{!-- template-lint-disable require-context-role --}}
|
|
||||||
>
|
|
||||||
{{d-icon tab.icon}}
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -136,6 +136,22 @@ const CORE_TOP_TABS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const CORE_BOTTOM_TABS = [
|
||||||
|
class extends UserMenuTab {
|
||||||
|
get id() {
|
||||||
|
return "profile";
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
get panelComponent() {
|
||||||
|
return "user-menu/profile-tab-content";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default class UserMenu extends Component {
|
export default class UserMenu extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
|
@ -185,8 +201,17 @@ export default class UserMenu extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
get _bottomTabs() {
|
get _bottomTabs() {
|
||||||
|
const tabs = [];
|
||||||
|
|
||||||
|
CORE_BOTTOM_TABS.forEach((tabClass) => {
|
||||||
|
const tab = new tabClass(this.currentUser, this.siteSettings, this.site);
|
||||||
|
if (tab.shouldDisplay) {
|
||||||
|
tabs.push(tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const topTabsLength = this.topTabs.length;
|
const topTabsLength = this.topTabs.length;
|
||||||
return this._coreBottomTabs.map((tab, index) => {
|
return tabs.map((tab, index) => {
|
||||||
tab.position = index + topTabsLength;
|
tab.position = index + topTabsLength;
|
||||||
return tab;
|
return tab;
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
<ul>
|
||||||
|
<li class="summary">
|
||||||
|
<LinkTo @route="user.summary" @model={{this.currentUser}}>
|
||||||
|
{{d-icon "user"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "user.summary.title"}}
|
||||||
|
</span>
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="activity">
|
||||||
|
<LinkTo @route="userActivity" @model={{this.currentUser}}>
|
||||||
|
{{d-icon "stream"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "user.activity_stream"}}
|
||||||
|
</span>
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{{#if this.currentUser.can_invite_to_forum}}
|
||||||
|
<li class="invites">
|
||||||
|
<LinkTo @route="userInvited" @model={{this.currentUser}}>
|
||||||
|
{{d-icon "user-plus"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "user.invited.title"}}
|
||||||
|
</span>
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<li class="drafts">
|
||||||
|
<LinkTo @route="userActivity.drafts" @model={{this.currentUser}}>
|
||||||
|
{{d-icon "pencil-alt"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{#if this.currentUser.draft_count}}
|
||||||
|
{{i18n "drafts.label_with_count" count=this.currentUser.draft_count}}
|
||||||
|
{{else}}
|
||||||
|
{{i18n "drafts.label"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="preferences">
|
||||||
|
<LinkTo @route="preferences" @model={{this.currentUser}}>
|
||||||
|
{{d-icon "cog"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "user.preferences"}}
|
||||||
|
</span>
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="do-not-disturb">
|
||||||
|
<DButton @class="btn-flat profile-tab-btn" @action={{this.doNotDisturbClick}}>
|
||||||
|
{{d-icon (if this.isInDoNotDisturb "toggle-on" "toggle-off")}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{#if this.isInDoNotDisturb}}
|
||||||
|
<span>{{i18n "do_not_disturb.label"}}</span>
|
||||||
|
<span
|
||||||
|
title={{this.doNotDisturbDateTitle}}
|
||||||
|
data-time={{this.doNotDisturbDateTime}}
|
||||||
|
data-format="tiny"
|
||||||
|
class="relative-date"
|
||||||
|
>
|
||||||
|
{{this.doNotDisturbDateContent}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
{{i18n "do_not_disturb.label"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</DButton>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{{#if this.showToggleAnonymousButton}}
|
||||||
|
<li class={{if this.currentUser.is_anonymous "disable-anonymous" "enable-anonymous"}}>
|
||||||
|
<DButton @class="btn-flat profile-tab-btn" @action={{route-action "toggleAnonymous"}}>
|
||||||
|
{{#if this.currentUser.is_anonymous}}
|
||||||
|
{{d-icon "ban"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "switch_from_anon"}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "user-secret"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "switch_to_anon"}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</DButton>
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<li class="logout">
|
||||||
|
<DButton @class="btn-flat profile-tab-btn" @action={{route-action "logout"}}>
|
||||||
|
{{d-icon "sign-out-alt"}}
|
||||||
|
<span class="item-label">
|
||||||
|
{{i18n "user.log_out"}}
|
||||||
|
</span>
|
||||||
|
</DButton>
|
||||||
|
</li>
|
||||||
|
</ul>
|
|
@ -0,0 +1,63 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import { longDate, relativeAge } from "discourse/lib/formatter";
|
||||||
|
|
||||||
|
export default class UserMenuProfileTabContent extends Component {
|
||||||
|
@service currentUser;
|
||||||
|
@service siteSettings;
|
||||||
|
saving = false;
|
||||||
|
|
||||||
|
get showToggleAnonymousButton() {
|
||||||
|
return (
|
||||||
|
(this.siteSettings.allow_anonymous_posting &&
|
||||||
|
this.currentUser.trust_level >=
|
||||||
|
this.siteSettings.anonymous_posting_min_trust_level) ||
|
||||||
|
this.currentUser.is_anonymous
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isInDoNotDisturb() {
|
||||||
|
return !!this.#doNotDisturbUntilDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
get doNotDisturbDateTitle() {
|
||||||
|
return longDate(this.#doNotDisturbUntilDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
get doNotDisturbDateContent() {
|
||||||
|
return relativeAge(this.#doNotDisturbUntilDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
get doNotDisturbDateTime() {
|
||||||
|
return this.#doNotDisturbUntilDate.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
get #doNotDisturbUntilDate() {
|
||||||
|
if (!this.currentUser.get("do_not_disturb_until")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const date = new Date(this.currentUser.get("do_not_disturb_until"));
|
||||||
|
if (date < new Date()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
doNotDisturbClick() {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saving = true;
|
||||||
|
if (this.currentUser.do_not_disturb_until) {
|
||||||
|
return this.currentUser.leaveDoNotDisturb().finally(() => {
|
||||||
|
this.saving = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.saving = false;
|
||||||
|
showModal("do-not-disturb");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<button
|
||||||
|
class={{concat "btn btn-flat btn-icon no-text" (if (eq @tab.id @currentTabId) " active")}}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
id={{concat "user-menu-button-" @tab.id}}
|
||||||
|
tabindex={{if (eq @tab.id @currentTabId) "0" "-1"}}
|
||||||
|
aria-selected={{if (eq @tab.id @currentTabId) "true" "false"}}
|
||||||
|
aria-controls={{concat "quick-access-" @tab.id}}
|
||||||
|
data-tab-number={{@tab.position}}
|
||||||
|
{{on "click" @changeTabFunction}}
|
||||||
|
{{!-- template-lint-disable require-context-role --}}
|
||||||
|
>
|
||||||
|
{{d-icon @tab.icon}}
|
||||||
|
{{yield}}
|
||||||
|
</button>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import templateOnly from "@ember/component/template-only";
|
||||||
|
|
||||||
|
export default templateOnly();
|
|
@ -100,3 +100,113 @@ acceptance("Do not disturb", function (needs) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
acceptance("Do not disturb - new user menu", function (needs) {
|
||||||
|
needs.user({ redesigned_user_menu_enabled: true });
|
||||||
|
needs.pretender((server, helper) => {
|
||||||
|
server.post("/do-not-disturb.json", () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(now.getHours() + 1);
|
||||||
|
return helper.response({ ends_at: now });
|
||||||
|
});
|
||||||
|
server.delete("/do-not-disturb.json", () =>
|
||||||
|
helper.response({ success: true })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("when turned off, it is turned on from modal", async function (assert) {
|
||||||
|
updateCurrentUser({ do_not_disturb_until: null });
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await click(".header-dropdown-toggle.current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
await click("#quick-access-profile .do-not-disturb .btn");
|
||||||
|
|
||||||
|
assert.ok(exists(".do-not-disturb-modal"), "modal to choose time appears");
|
||||||
|
|
||||||
|
let tiles = queryAll(".do-not-disturb-tile");
|
||||||
|
assert.ok(tiles.length === 4, "There are 4 duration choices");
|
||||||
|
|
||||||
|
await click(tiles[0]);
|
||||||
|
|
||||||
|
assert.ok(query(".do-not-disturb-modal.hidden"), "modal is hidden");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".header-dropdown-toggle .do-not-disturb-background .d-icon-moon"),
|
||||||
|
"moon icon is present in header"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Can be invoked via keyboard", async function (assert) {
|
||||||
|
updateCurrentUser({ do_not_disturb_until: null });
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await click(".header-dropdown-toggle.current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
await click("#quick-access-profile .do-not-disturb .btn");
|
||||||
|
|
||||||
|
assert.ok(exists(".do-not-disturb-modal"), "DND modal is displayed");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
count(".do-not-disturb-tile"),
|
||||||
|
4,
|
||||||
|
"There are 4 duration choices"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent(
|
||||||
|
".do-not-disturb-tile:nth-child(1)",
|
||||||
|
"keydown",
|
||||||
|
"Enter"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
query(".do-not-disturb-modal.hidden"),
|
||||||
|
"DND modal is hidden after making a choice"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".header-dropdown-toggle .do-not-disturb-background .d-icon-moon"),
|
||||||
|
"moon icon is shown in header avatar"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("when turned on, it can be turned off", async function (assert) {
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(now.getHours() + 1);
|
||||||
|
updateCurrentUser({ do_not_disturb_until: now });
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".do-not-disturb-background"),
|
||||||
|
"The active moon icon is shown"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".header-dropdown-toggle.current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".do-not-disturb .relative-date").textContent.trim(),
|
||||||
|
"1h",
|
||||||
|
"the Do Not Disturb button shows how much time is left for DND mode"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(".do-not-disturb .d-icon-toggle-on"),
|
||||||
|
"the Do Not Disturb button has the toggle-on icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("#quick-access-profile .do-not-disturb .btn");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists(".do-not-disturb-background"),
|
||||||
|
"The active moon icons are removed"
|
||||||
|
);
|
||||||
|
assert.notOk(
|
||||||
|
exists(".do-not-disturb .relative-date"),
|
||||||
|
"the text showing how much time is left for DND mode is gone"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(".do-not-disturb .d-icon-toggle-off"),
|
||||||
|
"the Do Not Disturb button has the toggle-off icon"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
publishToMessageBus,
|
publishToMessageBus,
|
||||||
query,
|
query,
|
||||||
queryAll,
|
queryAll,
|
||||||
|
updateCurrentUser,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
|
@ -19,7 +20,14 @@ acceptance("User menu", function (needs) {
|
||||||
needs.user({
|
needs.user({
|
||||||
redesigned_user_menu_enabled: true,
|
redesigned_user_menu_enabled: true,
|
||||||
unread_high_priority_notifications: 73,
|
unread_high_priority_notifications: 73,
|
||||||
|
trust_level: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
needs.settings({
|
||||||
|
allow_anonymous_posting: true,
|
||||||
|
anonymous_posting_min_trust_level: 3,
|
||||||
|
});
|
||||||
|
|
||||||
let requestHeaders = {};
|
let requestHeaders = {};
|
||||||
|
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
|
@ -178,6 +186,238 @@ acceptance("User menu", function (needs) {
|
||||||
"the tab's content is now displayed in the panel"
|
"the tab's content is now displayed in the panel"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the profile tab", async function (assert) {
|
||||||
|
updateCurrentUser({ draft_count: 13 });
|
||||||
|
await visit("/");
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
const summaryLink = query("#quick-access-profile ul li.summary a");
|
||||||
|
assert.ok(
|
||||||
|
summaryLink.href.endsWith("/u/eviltrout/summary"),
|
||||||
|
"has a link to the summary page of the user"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
summaryLink.textContent.trim(),
|
||||||
|
I18n.t("user.summary.title"),
|
||||||
|
"summary link has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
summaryLink.querySelector(".d-icon-user"),
|
||||||
|
"summary link has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
const activityLink = query("#quick-access-profile ul li.activity a");
|
||||||
|
assert.ok(
|
||||||
|
activityLink.href.endsWith("/u/eviltrout/activity"),
|
||||||
|
"has a link to the activity page of the user"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
activityLink.textContent.trim(),
|
||||||
|
I18n.t("user.activity_stream"),
|
||||||
|
"activity link has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
activityLink.querySelector(".d-icon-stream"),
|
||||||
|
"activity link has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
const invitesLink = query("#quick-access-profile ul li.invites a");
|
||||||
|
assert.ok(
|
||||||
|
invitesLink.href.endsWith("/u/eviltrout/invited"),
|
||||||
|
"has a link to the invites page of the user"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
invitesLink.textContent.trim(),
|
||||||
|
I18n.t("user.invited.title"),
|
||||||
|
"invites link has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
invitesLink.querySelector(".d-icon-user-plus"),
|
||||||
|
"invites link has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ can_invite_to_forum: false });
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists("#quick-access-profile ul li.invites"),
|
||||||
|
"invites link not shown when the user can't invite"
|
||||||
|
);
|
||||||
|
|
||||||
|
const dratsLink = query("#quick-access-profile ul li.drafts a");
|
||||||
|
assert.ok(
|
||||||
|
dratsLink.href.endsWith("/u/eviltrout/activity/drafts"),
|
||||||
|
"has a link to the drafts page of the user"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
dratsLink.textContent.trim(),
|
||||||
|
I18n.t("drafts.label_with_count", { count: 13 }),
|
||||||
|
"drafts link has the right label with count of the user's drafts"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
dratsLink.querySelector(".d-icon-pencil-alt"),
|
||||||
|
"drafts link has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
const preferencesLink = query("#quick-access-profile ul li.preferences a");
|
||||||
|
assert.ok(
|
||||||
|
preferencesLink.href.endsWith("/u/eviltrout/preferences"),
|
||||||
|
"has a link to the preferences page of the user"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
preferencesLink.textContent.trim(),
|
||||||
|
I18n.t("user.preferences"),
|
||||||
|
"preferences link has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
preferencesLink.querySelector(".d-icon-cog"),
|
||||||
|
"preferences link has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
let doNotDisturbButton = query(
|
||||||
|
"#quick-access-profile ul li.do-not-disturb .btn"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
doNotDisturbButton.textContent
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(/\u200B/g, "")
|
||||||
|
.trim(),
|
||||||
|
I18n.t("do_not_disturb.label"),
|
||||||
|
"Do Not Disturb button has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
doNotDisturbButton.querySelector(".d-icon-toggle-off"),
|
||||||
|
"Do Not Disturb button has the right icon"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
const date = new Date();
|
||||||
|
date.setHours(date.getHours() + 2);
|
||||||
|
updateCurrentUser({ do_not_disturb_until: date.toISOString() });
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
doNotDisturbButton = query(
|
||||||
|
"#quick-access-profile ul li.do-not-disturb .btn"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
doNotDisturbButton.textContent
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(/\u200B/g, "")
|
||||||
|
.trim(),
|
||||||
|
`${I18n.t("do_not_disturb.label")} 2h`,
|
||||||
|
"Do Not Disturb button has the right label when Do Not Disturb is enabled"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
doNotDisturbButton.querySelector(".d-icon-toggle-on"),
|
||||||
|
"Do Not Disturb button has the right icon when Do Not Disturb is enabled"
|
||||||
|
);
|
||||||
|
|
||||||
|
let toggleAnonButton = query(
|
||||||
|
"#quick-access-profile ul li.enable-anonymous .btn"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
toggleAnonButton.textContent
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(/\u200B/g, "")
|
||||||
|
.trim(),
|
||||||
|
I18n.t("switch_to_anon"),
|
||||||
|
"toggle anonymous button has the right label when the user isn't anonymous"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
toggleAnonButton.querySelector(".d-icon-user-secret"),
|
||||||
|
"toggle anonymous button has the right icon when the user isn't anonymous"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ is_anonymous: true });
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
toggleAnonButton = query(
|
||||||
|
"#quick-access-profile ul li.disable-anonymous .btn"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
toggleAnonButton.textContent
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(/\u200B/g, "")
|
||||||
|
.trim(),
|
||||||
|
I18n.t("switch_from_anon"),
|
||||||
|
"toggle anonymous button has the right label when the user is anonymous"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
toggleAnonButton.querySelector(".d-icon-ban"),
|
||||||
|
"toggle anonymous button has the right icon when the user is anonymous"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ is_anonymous: false, trust_level: 2 });
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists("#quick-access-profile ul li.enable-anonymous"),
|
||||||
|
"toggle anon button isn't shown when the user can't use it"
|
||||||
|
);
|
||||||
|
assert.notOk(
|
||||||
|
exists("#quick-access-profile ul li.disable-anonymous"),
|
||||||
|
"toggle anon button isn't shown when the user can't use it"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ is_anonymous: true, trust_level: 2 });
|
||||||
|
this.siteSettings.allow_anonymous_posting = false;
|
||||||
|
this.siteSettings.anonymous_posting_min_trust_level = 3;
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists("#quick-access-profile ul li.disable-anonymous"),
|
||||||
|
"toggle anon button is always shown if the user is anonymous"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ is_anonymous: false, trust_level: 4 });
|
||||||
|
this.siteSettings.allow_anonymous_posting = false;
|
||||||
|
this.siteSettings.anonymous_posting_min_trust_level = 3;
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists("#quick-access-profile ul li.enable-anonymous"),
|
||||||
|
"toggle anon button is not shown if the allow_anonymous_posting setting is false"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("header.d-header"); // close the menu
|
||||||
|
updateCurrentUser({ is_anonymous: false, trust_level: 2 });
|
||||||
|
this.siteSettings.allow_anonymous_posting = true;
|
||||||
|
this.siteSettings.anonymous_posting_min_trust_level = 3;
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-profile");
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
exists("#quick-access-profile ul li.enable-anonymous"),
|
||||||
|
"toggle anon button is not shown if the user doesn't have a high enough trust level"
|
||||||
|
);
|
||||||
|
|
||||||
|
const logoutButton = query("#quick-access-profile ul li.logout .btn");
|
||||||
|
assert.strictEqual(
|
||||||
|
logoutButton.textContent
|
||||||
|
.replaceAll(/\s+/g, " ")
|
||||||
|
.replaceAll(/\u200B/g, "")
|
||||||
|
.trim(),
|
||||||
|
I18n.t("user.log_out"),
|
||||||
|
"logout button has the right label"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
logoutButton.querySelector(".d-icon-sign-out-alt"),
|
||||||
|
"logout button has the right icon"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
acceptance("User menu - Dismiss button", function (needs) {
|
acceptance("User menu - Dismiss button", function (needs) {
|
||||||
|
|
|
@ -164,8 +164,9 @@ module("Integration | Component | site-header", function (hooks) {
|
||||||
await triggerKeyEvent(document, "keydown", "ArrowDown");
|
await triggerKeyEvent(document, "keydown", "ArrowDown");
|
||||||
|
|
||||||
focusedTab = document.activeElement;
|
focusedTab = document.activeElement;
|
||||||
assert.ok(
|
assert.strictEqual(
|
||||||
focusedTab.href.endsWith("/u/eviltrout/preferences"),
|
focusedTab.id,
|
||||||
|
"user-menu-button-profile",
|
||||||
"the down arrow key can move the focus to the bottom tabs"
|
"the down arrow key can move the focus to the bottom tabs"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -179,8 +180,9 @@ module("Integration | Component | site-header", function (hooks) {
|
||||||
|
|
||||||
await triggerKeyEvent(document, "keydown", "ArrowUp");
|
await triggerKeyEvent(document, "keydown", "ArrowUp");
|
||||||
focusedTab = document.activeElement;
|
focusedTab = document.activeElement;
|
||||||
assert.ok(
|
assert.strictEqual(
|
||||||
focusedTab.href.endsWith("/u/eviltrout/preferences"),
|
focusedTab.id,
|
||||||
|
"user-menu-button-profile",
|
||||||
"the up arrow key moves the focus in the opposite direction"
|
"the up arrow key moves the focus in the opposite direction"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,10 +70,10 @@ module("Integration | Component | user-menu", function (hooks) {
|
||||||
await render(template);
|
await render(template);
|
||||||
const tabs = queryAll(".bottom-tabs.tabs-list .btn");
|
const tabs = queryAll(".bottom-tabs.tabs-list .btn");
|
||||||
assert.strictEqual(tabs.length, 1);
|
assert.strictEqual(tabs.length, 1);
|
||||||
const preferencesTab = tabs[0];
|
const profileTab = tabs[0];
|
||||||
assert.ok(preferencesTab.href.endsWith("/u/eviltrout/preferences"));
|
assert.strictEqual(profileTab.id, "user-menu-button-profile");
|
||||||
assert.strictEqual(preferencesTab.dataset.tabNumber, "6");
|
assert.strictEqual(profileTab.dataset.tabNumber, "6");
|
||||||
assert.strictEqual(preferencesTab.getAttribute("tabindex"), "-1");
|
assert.strictEqual(profileTab.getAttribute("tabindex"), "-1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("likes tab is hidden if current user's like notifications frequency is 'never'", async function (assert) {
|
test("likes tab is hidden if current user's like notifications frequency is 'never'", async function (assert) {
|
||||||
|
|
|
@ -94,6 +94,28 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
#quick-access-profile {
|
||||||
|
ul {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
overflow-y: auto; // really short viewports
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-height: 3em; // prevent buttons from getting too tall
|
||||||
|
> * {
|
||||||
|
// button, a, and everything else
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
.d-icon {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.panel-body-bottom {
|
.panel-body-bottom {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
|
@ -102,9 +124,8 @@
|
||||||
.menu-tabs-container {
|
.menu-tabs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
border-left: 1px solid var(--primary-low);
|
border-left: 1px solid var(--primary-low);
|
||||||
padding: 0.75em 0;
|
padding: 0.75em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-list {
|
.tabs-list {
|
||||||
|
@ -137,6 +158,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-tabs {
|
||||||
|
border-top: 1px solid var(--primary-low);
|
||||||
|
}
|
||||||
|
|
||||||
.panel-body-contents {
|
.panel-body-contents {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -179,6 +204,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#quick-access-profile {
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
.profile-tab-btn {
|
||||||
|
justify-content: unset;
|
||||||
|
line-height: $line-height-large;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.do-not-disturb {
|
||||||
|
.relative-date {
|
||||||
|
font-size: $font-down-3;
|
||||||
|
color: var(--primary-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon-toggle-on {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove when the widgets-based implementation of the user menu is removed
|
// remove when the widgets-based implementation of the user menu is removed
|
||||||
|
@ -399,7 +449,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a,
|
||||||
|
.profile-tab-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0.25em;
|
margin: 0.25em;
|
||||||
padding: 0em 0.25em;
|
padding: 0em 0.25em;
|
||||||
|
|
Loading…
Reference in New Issue