DEV: Ship first pass of new user page navigation behind feature flag (#18285)
This commits introduces a new SiteSetting.enable_new_user_profile_nav_groups feature flag. When configured, users of the configured groups will see the new user page navigation links. As of this commit, only the user activity navigation link has been converted to the newly proposed dropdown of navigation links. Mobile support has not been considered.
This commit is contained in:
parent
1413de2809
commit
da3e72c2b4
|
@ -0,0 +1,100 @@
|
|||
<section class="user-primary-navigation">
|
||||
<ul class="main-nav nav nav-pills user-nav">
|
||||
{{#unless @user.profile_hidden}}
|
||||
<li class="summary">
|
||||
<LinkTo @route="user.summary">
|
||||
{{d-icon "user"}}
|
||||
{{i18n "user.summary.title"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
|
||||
<UserNav::DropdownList
|
||||
@icon="stream"
|
||||
@text={{i18n "user.activity_stream"}}
|
||||
@isActive={{eq @currentParentRoute "userActivity"}}
|
||||
@class="user-activity" >
|
||||
|
||||
<:submenu>
|
||||
<DNavigationItem @route="userActivity.index">{{i18n "user.filters.all"}}</DNavigationItem>
|
||||
<DNavigationItem @route="userActivity.topics">{{i18n "user_action_groups.4"}}</DNavigationItem>
|
||||
<DNavigationItem @route="userActivity.replies">{{i18n "user_action_groups.5"}}</DNavigationItem>
|
||||
|
||||
{{#if @showRead}}
|
||||
<DNavigationItem @route="userActivity.read" @title={{i18n "user.read_help"}}>
|
||||
{{i18n "user.read"}}
|
||||
</DNavigationItem>
|
||||
{{/if}}
|
||||
|
||||
{{#if @showDrafts}}
|
||||
<DNavigationItem @route="userActivity.drafts">
|
||||
{{this.draftLabel}}
|
||||
</DNavigationItem>
|
||||
{{/if}}
|
||||
|
||||
{{#if (gt @user.pending_posts_count 0)}}
|
||||
<DNavigationItem @route="userActivity.pending">
|
||||
{{this.pendingLabel}}
|
||||
</DNavigationItem>
|
||||
{{/if}}
|
||||
|
||||
<DNavigationItem @route="userActivity.likesGiven">{{i18n "user_action_groups.1"}}</DNavigationItem>
|
||||
|
||||
{{#if @showBookmarks}}
|
||||
<DNavigationItem @route="userActivity.bookmarks">{{i18n "user_action_groups.3"}}</DNavigationItem>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet @name="user-activity-bottom" @tagName="span" @connectorTagName="li" @args={{hash model=@user}} />
|
||||
</:submenu>
|
||||
</UserNav::DropdownList>
|
||||
{{/unless}}
|
||||
|
||||
{{#if @showNotificationsTab}}
|
||||
<li class="user-notifications">
|
||||
<LinkTo @route="userNotifications">
|
||||
{{d-icon "comment" class="glyph"}}{{i18n "user.notifications"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if @showPrivateMessages}}
|
||||
<li class="private-messages">
|
||||
<LinkTo @route="userPrivateMessages">
|
||||
{{d-icon "far-envelope"}}
|
||||
{{i18n "user.private_messages"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if @canInviteToForum}}
|
||||
<li class="invited">
|
||||
<LinkTo @route="userInvited">
|
||||
{{d-icon "user-plus"}}
|
||||
{{i18n "user.invited.title"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if @showBadges}}
|
||||
<li class="badges">
|
||||
<LinkTo @route="user.badges">
|
||||
{{d-icon "certificate"}}
|
||||
{{i18n "badges.title"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="user-main-nav"
|
||||
@connectorTagName="li"
|
||||
@args={{hash model=@user}} />
|
||||
|
||||
{{#if @user.can_edit}}
|
||||
<li class="preferences">
|
||||
<LinkTo @route="preferences">
|
||||
{{d-icon "cog"}}
|
||||
{{i18n "user.preferences"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</section>
|
|
@ -0,0 +1,18 @@
|
|||
import I18n from "I18n";
|
||||
|
||||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class UserNav extends Component {
|
||||
@service currentUser;
|
||||
@service site;
|
||||
@service router;
|
||||
|
||||
get draftLabel() {
|
||||
const count = this.currentUser.draft_count;
|
||||
|
||||
return count > 0
|
||||
? I18n.t("drafts.label_with_count", { count })
|
||||
: I18n.t("drafts.label");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<li class={{concat @class " user-nav-dropdown-list-item"}}>
|
||||
<button type="button" class={{this.buttonClass}} {{on "click" this.toggleList}}>
|
||||
{{d-icon @icon}}
|
||||
<span>{{@text}}</span>
|
||||
{{d-icon this.chevron class="user-nav-dropdown-chevron"}}
|
||||
</button>
|
||||
|
||||
{{#if (and (has-block "submenu") this.displayList)}}
|
||||
<div class="user-nav-dropdown-submenu-wrapper"
|
||||
{{did-insert this.registerClickListener}}
|
||||
{{will-destroy this.deregisterClickListener}} >
|
||||
|
||||
<ul class={{concat @submenuClass " user-nav-dropdown-submenu"}}>
|
||||
{{yield to="submenu"}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
|
@ -0,0 +1,54 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default class UserNavDropdownList extends Component {
|
||||
@tracked displayList = false;
|
||||
|
||||
get chevron() {
|
||||
return this.displayList ? "chevron-up" : "chevron-down";
|
||||
}
|
||||
|
||||
get defaultButtonClass() {
|
||||
return "user-nav-dropdown-button";
|
||||
}
|
||||
|
||||
get buttonClass() {
|
||||
const props = [this.defaultButtonClass];
|
||||
|
||||
if (this.args.isActive) {
|
||||
props.push("active");
|
||||
}
|
||||
|
||||
return props.join(" ");
|
||||
}
|
||||
|
||||
@action
|
||||
toggleList() {
|
||||
this.displayList = !this.displayList;
|
||||
}
|
||||
|
||||
@bind
|
||||
collapseList(e) {
|
||||
const isClickOnButton = e.composedPath().some((element) => {
|
||||
if (element?.classList?.contains(this.defaultButtonClass)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isClickOnButton) {
|
||||
this.displayList = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
registerClickListener() {
|
||||
document.addEventListener("click", this.collapseList);
|
||||
}
|
||||
|
||||
@action
|
||||
deregisterClickListener() {
|
||||
document.removeEventListener("click", this.collapseList);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import EmberObject, { computed, set } from "@ember/object";
|
||||
import { and, equal, gt, not, or } from "@ember/object/computed";
|
||||
import { and, equal, gt, not, or, readOnly } from "@ember/object/computed";
|
||||
import CanCheckEmails from "discourse/mixins/can-check-emails";
|
||||
import User from "discourse/models/user";
|
||||
import I18n from "I18n";
|
||||
|
@ -164,6 +164,8 @@ export default Controller.extend(CanCheckEmails, {
|
|||
}
|
||||
},
|
||||
|
||||
currentParentRoute: readOnly("router.currentRoute.parent.name"),
|
||||
|
||||
userNotificationLevel: computed(
|
||||
"currentUser.ignored_ids",
|
||||
"model.ignored",
|
||||
|
|
|
@ -224,24 +224,33 @@
|
|||
{{/unless}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if this.currentUser.redesigned_user_page_nav_enabled}}
|
||||
<div class="new-user-wrapper">
|
||||
<UserNav
|
||||
@user={{this.model}}
|
||||
@showNotificationsTab={{this.showNotificationsTab}}
|
||||
@showPrivateMessages={{this.showPrivateMessages}}
|
||||
@canInviteToForum={{this.canInviteToForum}}
|
||||
@showBadges={{this.showBadges}}
|
||||
@currentParentRoute={{this.currentParentRoute}}
|
||||
@showRead={{this.showRead}}
|
||||
@showDrafts={{this.showDrafts}}
|
||||
@showBookmarks={{this.showBookmarks}} />
|
||||
|
||||
<div class="new-user-content-wrapper">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class='user-content-wrapper'>
|
||||
<section class="user-primary-navigation">
|
||||
<MobileNav @class="main-nav" @desktopClass="nav nav-pills user-nav">
|
||||
{{#unless this.model.profile_hidden}}
|
||||
<li class="summary">
|
||||
<LinkTo @route="user.summary">
|
||||
{{d-icon "user"}}
|
||||
{{i18n 'user.summary.title'}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
|
||||
<li class="activity">
|
||||
<LinkTo @route="userActivity">
|
||||
{{d-icon "stream"}}
|
||||
{{i18n 'user.activity_stream'}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="summary"><LinkTo @route="user.summary">{{i18n 'user.summary.title'}}</LinkTo></li>
|
||||
<li class="activity"><LinkTo @route="userActivity">{{i18n 'user.activity_stream'}}</LinkTo></li>
|
||||
{{/unless}}
|
||||
|
||||
{{#if this.showNotificationsTab}}
|
||||
<li class="user-notifications">
|
||||
<LinkTo @route="userNotifications">
|
||||
|
@ -249,22 +258,29 @@
|
|||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showPrivateMessages}}
|
||||
<li class="private-messages"><LinkTo @route="userPrivateMessages">{{d-icon "far-envelope"}}{{i18n 'user.private_messages'}}</LinkTo></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.canInviteToForum}}
|
||||
<li class="invited"><LinkTo @route="userInvited">{{d-icon "user-plus"}}{{i18n 'user.invited.title'}}</LinkTo></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showBadges}}
|
||||
<li class="badges"><LinkTo @route="user.badges">{{d-icon "certificate"}}{{i18n 'badges.title'}}</LinkTo></li>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet @name="user-main-nav" @connectorTagName="li" @args={{hash model=this.model}} />
|
||||
|
||||
{{#if this.model.can_edit}}
|
||||
<li class="preferences"><LinkTo @route="preferences">{{d-icon "cog"}}{{i18n 'user.preferences'}}</LinkTo></li>
|
||||
{{/if}}
|
||||
</MobileNav>
|
||||
</section>
|
||||
|
||||
{{outlet}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</DSection>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<DSection @pageClass="user-activity" @class="user-secondary-navigation" @scrollTop={{false}}>
|
||||
{{#unless this.currentUser.redesigned_user_page_nav_enabled}}
|
||||
<DSection @pageClass="user-activity" @class="user-secondary-navigation" @scrollTop={{false}}>
|
||||
<nav role="navigation">
|
||||
<MobileNav @class="activity-nav" @desktopClass="action-list activity-list nav-stacked">
|
||||
<DNavigationItem @route="userActivity.index">{{i18n "user.filters.all"}}</DNavigationItem>
|
||||
|
@ -32,12 +33,14 @@
|
|||
<PluginOutlet @name="user-activity-bottom" @tagName="span" @connectorTagName="li" @args={{hash model=this.model}} />
|
||||
</MobileNav>
|
||||
</nav>
|
||||
</DSection>
|
||||
{{#if this.canDownloadPosts}}
|
||||
</DSection>
|
||||
|
||||
{{#if this.canDownloadPosts}}
|
||||
<section class="user-additional-controls">
|
||||
<DButton @action={{action "exportUserArchive"}} @class="btn-default" @label="user.download_archive.button_text" @icon="download" />
|
||||
</section>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
<section class="user-content">
|
||||
{{outlet}}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
@import "magnific-popup";
|
||||
@import "menu-panel";
|
||||
@import "modal";
|
||||
@import "new-user";
|
||||
@import "not-found";
|
||||
@import "onebox";
|
||||
@import "personal-message";
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
.new-user-wrapper {
|
||||
.new-user-content-wrapper {
|
||||
// Grid layout
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-gap: 20px;
|
||||
|
||||
.user-secondary-navigation {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 2;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 2;
|
||||
}
|
||||
|
||||
.user-content {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 3;
|
||||
}
|
||||
|
||||
.user-additional-controls {
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
grid-row-start: 2;
|
||||
}
|
||||
|
||||
.user-secondary-navigation ~ .user-content {
|
||||
grid-column-start: 2;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.user-nav-dropdown-list-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-nav-dropdown-button {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.user-nav-dropdown-submenu-wrapper {
|
||||
position: absolute;
|
||||
top: 2em;
|
||||
min-width: 10em;
|
||||
padding: 0;
|
||||
box-shadow: shadow("dropdown");
|
||||
z-index: z("dropdown");
|
||||
}
|
||||
|
||||
.user-nav-dropdown-submenu {
|
||||
background: var(--secondary);
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
|
||||
li a {
|
||||
padding: 0.5em 1em;
|
||||
color: var(--primary);
|
||||
.discourse-no-touch & {
|
||||
&:hover {
|
||||
background: var(--highlight-medium);
|
||||
color: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--tertiary-low);
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,8 @@
|
|||
display: flex;
|
||||
margin-right: 0.5em;
|
||||
|
||||
> a {
|
||||
> a,
|
||||
button {
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
color: var(--primary);
|
||||
|
@ -52,7 +53,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
a.active {
|
||||
a.active,
|
||||
button.active {
|
||||
color: var(--secondary);
|
||||
background-color: var(--quaternary);
|
||||
|
||||
|
|
|
@ -78,13 +78,15 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
:sidebar_category_ids,
|
||||
:likes_notifications_disabled,
|
||||
:grouped_unread_notifications,
|
||||
:redesigned_user_menu_enabled
|
||||
:redesigned_user_menu_enabled,
|
||||
:redesigned_user_page_nav_enabled
|
||||
|
||||
delegate :user_stat, to: :object, private: true
|
||||
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
|
||||
|
||||
def groups
|
||||
owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set
|
||||
|
||||
object.visible_groups.pluck(:id, :name, :has_messages).map do |id, name, has_messages|
|
||||
group = { id: id, name: name, has_messages: has_messages }
|
||||
group[:owner] = true if owned_group_ids.include?(id)
|
||||
|
@ -342,4 +344,12 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
def include_unseen_reviewable_count?
|
||||
redesigned_user_menu_enabled
|
||||
end
|
||||
|
||||
def redesigned_user_page_nav_enabled
|
||||
if SiteSetting.enable_new_user_profile_nav_groups.present?
|
||||
GroupUser.exists?(user_id: object.id, group_id: SiteSetting.enable_new_user_profile_nav_groups.split("|"))
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2002,6 +2002,14 @@ developer:
|
|||
type: tag_list
|
||||
default: ""
|
||||
client: true
|
||||
enable_new_user_profile_nav_groups:
|
||||
client: true
|
||||
type: group_list
|
||||
list_type: compact
|
||||
default: ""
|
||||
allow_any: false
|
||||
refresh: true
|
||||
hidden: true
|
||||
|
||||
embedding:
|
||||
embed_by_username:
|
||||
|
|
|
@ -350,4 +350,26 @@ RSpec.describe CurrentUserSerializer do
|
|||
expect(serializer.as_json[:likes_notifications_disabled]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#redesigned_user_page_nav_enabled' do
|
||||
fab!(:group) { Fabricate(:group) }
|
||||
fab!(:group2) { Fabricate(:group) }
|
||||
|
||||
it "is false when enable_new_user_profile_nav_groups site setting has not been set" do
|
||||
expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(false)
|
||||
end
|
||||
|
||||
it 'is false if user does not belong to any of the configured groups in the enable_new_user_profile_nav_groups site setting' do
|
||||
SiteSetting.enable_new_user_profile_nav_groups = "#{group.id}|#{group2.id}"
|
||||
|
||||
expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(false)
|
||||
end
|
||||
|
||||
it 'is true if user belongs one of the configured groups in the enable_new_user_profile_nav_groups site setting' do
|
||||
SiteSetting.enable_new_user_profile_nav_groups = "#{group.id}|#{group2.id}"
|
||||
group.add(user)
|
||||
|
||||
expect(serializer.as_json[:redesigned_user_page_nav_enabled]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue