DEV: Add admins and moderators sections to the redesigned /about page (#28273)

This commit continues on work laid out by 6039b513fe to redesign the /about page. In this commit, we add sections for showing the site admins and moderators.

The lists of admins and moderators display the 10 most recently seen admins/moderators, with a button to display the rest of admins or moderators. Admins or moderators that have not logged in to the site in the last year will not be shown. Clicking on an admin's or moderator's name/avatar will show their user card.
This commit is contained in:
Osama Sayegh 2024-08-12 16:23:44 +03:00 committed by GitHub
parent 34de336afb
commit 1d6e54e54c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 389 additions and 64 deletions

View File

@ -0,0 +1,47 @@
import avatar from "discourse/helpers/avatar";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { userPath } from "discourse/lib/url";
import i18n from "discourse-common/helpers/i18n";
const AboutPageUser = <template>
<div data-username={{@user.username}} class="user-info small">
<div class="user-image">
<div class="user-image-inner">
<a
href={{userPath @user.username}}
data-user-card={{@user.username}}
aria-hidden="true"
>
{{avatar @user imageSize="large"}}
</a>
</div>
</div>
<div class="user-detail">
<div class="name-line">
<a
href={{userPath @user.username}}
data-user-card={{@user.username}}
aria-label={{i18n "user.profile_possessive" username=@user.username}}
>
<span class="username">
{{#if (prioritizeNameInUx @user.name)}}
{{@user.name}}
{{else}}
{{@user.username}}
{{/if}}
</span>
<span class="name">
{{#if (prioritizeNameInUx @user.name)}}
{{@user.username}}
{{else}}
{{@user.name}}
{{/if}}
</span>
</a>
</div>
<div class="title">{{@user.title}}</div>
</div>
</div>
</template>;
export default AboutPageUser;

View File

@ -1,71 +1,49 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { renderAvatar } from "discourse/helpers/user-avatar";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { userPath } from "discourse/lib/url";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import AboutPageUser from "discourse/components/about-page-user";
import DButton from "discourse/components/d-button";
import i18n from "discourse-common/helpers/i18n";
export default class AboutPageUsers extends Component {
@service siteSettings;
@tracked expanded = false;
get usersTemplates() {
return (this.args.users || []).map((user) => ({
name: user.name,
username: user.username,
userPath: userPath(user.username),
avatar: renderAvatar(user, {
imageSize: "large",
siteSettings: this.siteSettings,
}),
title: user.title || "",
prioritizeName: prioritizeNameInUx(user.name),
}));
get users() {
let users = this.args.users;
if (this.showViewMoreButton && !this.expanded) {
users = users.slice(0, this.args.truncateAt);
}
return users;
}
get showViewMoreButton() {
return (
this.args.truncateAt > 0 && this.args.users.length > this.args.truncateAt
);
}
@action
toggleExpanded() {
this.expanded = !this.expanded;
}
<template>
{{#each this.usersTemplates as |template|}}
<div data-username={{template.username}} class="user-info small">
<div class="user-image">
<div class="user-image-inner">
<a
href={{template.userPath}}
data-user-card={{template.username}}
aria-hidden="true"
>
{{htmlSafe template.avatar}}
</a>
</div>
</div>
<div class="user-detail">
<div class="name-line">
<a
href={{template.userPath}}
data-user-card={{template.username}}
aria-label={{i18n
"user.profile_possessive"
username=template.username
}}
>
<span class="username">
{{#if template.prioritizeName}}
{{template.name}}
{{else}}
{{template.username}}
{{/if}}
</span>
<span class="name">
{{#if template.prioritizeName}}
{{template.username}}
{{else}}
{{template.name}}
{{/if}}
</span>
</a>
</div>
<div class="title">{{template.title}}</div>
</div>
</div>
{{/each}}
<div class="about-page-users-list">
{{#each this.users as |user|}}
<AboutPageUser @user={{user}} />
{{/each}}
</div>
{{#if this.showViewMoreButton}}
<DButton
class="btn-flat about-page-users-list__expand-button"
@action={{this.toggleExpanded}}
@icon={{if this.expanded "chevron-up" "chevron-down"}}
@translatedLabel={{if
this.expanded
(i18n "about.view_less")
(i18n "about.view_more")
}}
/>
{{/if}}
</template>
}

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { htmlSafe } from "@ember/template";
import AboutPageUsers from "discourse/components/about-page-users";
import PluginOutlet from "discourse/components/plugin-outlet";
import { number } from "discourse/lib/formatter";
import dIcon from "discourse-common/helpers/d-icon";
@ -161,6 +162,20 @@ export default class AboutPage extends Component {
</div>
<h3>{{i18n "about.simple_title"}}</h3>
<div>{{htmlSafe @model.extended_site_description}}</div>
{{#if @model.admins.length}}
<section class="about__admins">
<h3>{{dIcon "users"}} {{i18n "about.our_admins"}}</h3>
<AboutPageUsers @users={{@model.admins}} @truncateAt={{10}} />
</section>
{{/if}}
{{#if @model.moderators.length}}
<section class="about__moderators">
<h3>{{dIcon "users"}} {{i18n "about.our_moderators"}}</h3>
<AboutPageUsers @users={{@model.moderators}} @truncateAt={{10}} />
</section>
{{/if}}
</section>
<section class="about__right-side">
<h3>{{i18n "about.contact"}}</h3>

View File

@ -0,0 +1,14 @@
import Component from "@glimmer/component";
import AboutPageUser from "discourse/components/about-page-user";
export default class LegacyAboutPageUsers extends Component {
get users() {
return this.args.users || [];
}
<template>
{{#each this.users as |user|}}
<AboutPageUser @user={{user}} />
{{/each}}
</template>
}

View File

@ -53,7 +53,7 @@
<section class="about admins">
<h3>{{d-icon "users"}} {{i18n "about.our_admins"}}</h3>
<div class="users">
<AboutPageUsers @users={{this.model.admins}} />
<LegacyAboutPageUsers @users={{this.model.admins}} />
</div>
</section>
{{/if}}
@ -70,7 +70,7 @@
<section class="about moderators">
<h3>{{d-icon "users"}} {{i18n "about.our_moderators"}}</h3>
<div class="users">
<AboutPageUsers @users={{this.model.moderators}} />
<LegacyAboutPageUsers @users={{this.model.moderators}} />
</div>
</section>
{{/if}}
@ -90,7 +90,7 @@
>
<h3>{{category-link cm.category}}{{i18n "about.moderators"}}</h3>
<div class="users">
<AboutPageUsers @users={{cm.moderators}} />
<LegacyAboutPageUsers @users={{cm.moderators}} />
</div>
<div class="clearfix"></div>
</section>

View File

@ -37,6 +37,25 @@
&__activities-item-period {
font-size: var(--font-down-2);
}
&__admins,
&__moderators {
margin-top: 3em;
h3 {
margin-bottom: 1em;
}
}
}
.about-page-users-list {
display: grid;
gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
&__expand-button {
width: 100%;
}
}
section.about {

View File

@ -346,6 +346,8 @@ en:
contact: "Contact us"
contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_info}."
site_activity: "Site activity"
view_more: "View more"
view_less: "View less"
activities:
topics:
one: "%{formatted_number} topic"

View File

@ -171,5 +171,187 @@ describe "About page", type: :system do
end
end
end
describe "our admins section" do
before { User.update_all(last_seen_at: 1.month.ago) }
fab!(:admins) { Fabricate.times(12, :admin) }
it "displays only the 10 most recently seen admins when there are more than 10 admins" do
admins[0].update!(last_seen_at: 4.minutes.ago)
admins[1].update!(last_seen_at: 1.minutes.ago)
admins[2].update!(last_seen_at: 10.minutes.ago)
about_page.visit
expect(about_page.admins_list).to have_expand_button
displayed_admins = about_page.admins_list.users
expect(displayed_admins.size).to eq(10)
expect(displayed_admins.map { |u| u[:username] }.first(3)).to eq(
[admins[1].username, admins[0].username, admins[2].username],
)
end
it "allows expanding and collapsing the list of admins" do
about_page.visit
displayed_admins = about_page.admins_list.users
expect(displayed_admins.size).to eq(10)
expect(about_page.admins_list).to be_expandable
about_page.admins_list.expand
expect(about_page.admins_list).to be_collapsible
displayed_admins = about_page.admins_list.users
expect(displayed_admins.size).to eq(13) # 12 fabricated for this spec group and 1 global
about_page.admins_list.collapse
expect(about_page.admins_list).to be_expandable
displayed_admins = about_page.admins_list.users
expect(displayed_admins.size).to eq(10)
end
it "doesn't show an expand/collapse button when there are fewer than 10 admins" do
User.where(id: admins.first(7).map(&:id)).destroy_all
about_page.visit
displayed_admins = about_page.admins_list.users
expect(displayed_admins.size).to eq(6)
expect(about_page.admins_list).to have_no_expand_button
end
it "prioritizes names when prioritize_username_in_ux is false" do
SiteSetting.prioritize_username_in_ux = false
about_page.visit
displayed_admins = about_page.admins_list.users
admins = User.where(username: displayed_admins.map { |u| u[:username] })
expect(displayed_admins.map { |u| u[:displayed_username] }).to contain_exactly(
*admins.pluck(:name),
)
expect(displayed_admins.map { |u| u[:displayed_name] }).to contain_exactly(
*admins.pluck(:username),
)
end
it "prioritizes usernames when prioritize_username_in_ux is true" do
SiteSetting.prioritize_username_in_ux = true
about_page.visit
displayed_admins = about_page.admins_list.users
admins = User.where(username: displayed_admins.map { |u| u[:username] })
expect(displayed_admins.map { |u| u[:displayed_username] }).to contain_exactly(
*admins.pluck(:username),
)
expect(displayed_admins.map { |u| u[:displayed_name] }).to contain_exactly(
*admins.pluck(:name),
)
end
it "opens the user card when a user is clicked" do
about_page.visit
about_page.admins_list.users.first[:node].click
expect(about_page).to have_css("#user-card")
end
end
describe "our moderators section" do
before { User.update_all(last_seen_at: 1.month.ago) }
fab!(:moderators) { Fabricate.times(13, :moderator) }
it "displays only the 10 most recently seen moderators when there are more than 10 moderators" do
moderators[10].update!(last_seen_at: 5.hours.ago)
moderators[3].update!(last_seen_at: 2.hours.ago)
moderators[5].update!(last_seen_at: 13.hours.ago)
about_page.visit
expect(about_page.moderators_list).to have_expand_button
displayed_mods = about_page.moderators_list.users
expect(displayed_mods.size).to eq(10)
expect(displayed_mods.map { |u| u[:username] }.first(3)).to eq(
[moderators[3].username, moderators[10].username, moderators[5].username],
)
end
it "allows expanding and collapsing the list of moderators" do
about_page.visit
displayed_mods = about_page.moderators_list.users
expect(displayed_mods.size).to eq(10)
expect(about_page.moderators_list).to be_expandable
about_page.moderators_list.expand
expect(about_page.moderators_list).to be_collapsible
displayed_mods = about_page.moderators_list.users
expect(displayed_mods.size).to eq(14) # 13 fabricated for this spec group and 1 global
about_page.moderators_list.collapse
expect(about_page.moderators_list).to be_expandable
displayed_mods = about_page.moderators_list.users
expect(displayed_mods.size).to eq(10)
end
it "doesn't show an expand/collapse button when there are fewer than 10 moderators" do
User.where(id: moderators.first(10).map(&:id)).destroy_all
about_page.visit
displayed_mods = about_page.moderators_list.users
expect(displayed_mods.size).to eq(4)
expect(about_page.moderators_list).to have_no_expand_button
end
it "prioritizes names when prioritize_username_in_ux is false" do
SiteSetting.prioritize_username_in_ux = false
about_page.visit
displayed_mods = about_page.moderators_list.users
moderators = User.where(username: displayed_mods.map { |u| u[:username] })
expect(displayed_mods.map { |u| u[:displayed_username] }).to contain_exactly(
*moderators.pluck(:name),
)
expect(displayed_mods.map { |u| u[:displayed_name] }).to contain_exactly(
*moderators.pluck(:username),
)
end
it "prioritizes usernames when prioritize_username_in_ux is true" do
SiteSetting.prioritize_username_in_ux = true
about_page.visit
displayed_mods = about_page.moderators_list.users
moderators = User.where(username: displayed_mods.map { |u| u[:username] })
expect(displayed_mods.map { |u| u[:displayed_username] }).to contain_exactly(
*moderators.pluck(:username),
)
expect(displayed_mods.map { |u| u[:displayed_name] }).to contain_exactly(
*moderators.pluck(:name),
)
end
it "opens the user card when a user is clicked" do
about_page.visit
about_page.moderators_list.users.last[:node].click
expect(about_page).to have_css("#user-card")
end
end
end
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
module PageObjects
module Components
class AboutPageUsersList < PageObjects::Components::Base
attr_reader :container
def initialize(container)
@container = container
end
def has_expand_button?
container.has_css?(".about-page-users-list__expand-button")
end
def has_no_expand_button?
container.has_no_css?(".about-page-users-list__expand-button")
end
def expandable?
container.find(".about-page-users-list__expand-button").has_text?(
I18n.t("js.about.view_more"),
)
end
def collapsible?
container.find(".about-page-users-list__expand-button").has_text?(
I18n.t("js.about.view_less"),
)
end
def expand
container.find(
".about-page-users-list__expand-button",
text: I18n.t("js.about.view_more"),
).click
end
def collapse
container.find(
".about-page-users-list__expand-button",
text: I18n.t("js.about.view_less"),
).click
end
def users
container
.all(".user-info")
.map do |node|
{
username: node["data-username"],
displayed_username: node.find(".name-line .username").text,
displayed_name: node.find(".name-line .name").text,
node:,
}
end
end
end
end
end

View File

@ -50,6 +50,14 @@ module PageObjects
PageObjects::Components::AboutPageSiteActivity.new(find(".about__activities"))
end
def admins_list
PageObjects::Components::AboutPageUsersList.new(find(".about__admins"))
end
def moderators_list
PageObjects::Components::AboutPageUsersList.new(find(".about__moderators"))
end
private
def site_age_stat_element