DEV: backport outlet wrappers (#30110)

* DEV: add outlet wrapper for categories boxes (#28860)

* DEV: add outlet wrapper for category boxes

* Put plugin outlet after categories boxes

* DEV: Add outlet wrapper for badges template (#28928)

* DEV: Add outlet wrapper for badges template

* Apply suggestions from code review

Co-authored-by: Sérgio Saquetim <1108771+megothss@users.noreply.github.com>

---------

Co-authored-by: Sérgio Saquetim <1108771+megothss@users.noreply.github.com>

* DEV: Add aditional args to plugin outlet (#28948)

* DEV: Add outlet wrapper for user card information replacement (#29523)

* DEV: Add outlet wrapper for user card information replacement

* Fix format issues

* Fix format issues

* format file

* DEV: add outlet wrapper for small user list (#29763)

* DEV: add outlet wrapper for small user list

* DEV: use value transformer to extend small user attrs function

* Update app/assets/javascripts/discourse/app/components/small-user-list.gjs

Co-authored-by: Jarek Radosz <jradosz@gmail.com>

---------

Co-authored-by: Jarek Radosz <jradosz@gmail.com>

* Fix lint issue

* remove extra html

* remove extra value transformers

* disable template formatting rule

* remove aria hidden

---------

Co-authored-by: Sérgio Saquetim <1108771+megothss@users.noreply.github.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
This commit is contained in:
Amanda Alves Branquinho 2024-12-05 21:26:05 -03:00 committed by GitHub
parent 8a1c84b748
commit f71e1643db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 306 additions and 259 deletions

View File

@ -21,6 +21,7 @@
@outletArgs={{hash @outletArgs={{hash
availableBadges=this.availableBadges availableBadges=this.availableBadges
userBadges=this.userBadges userBadges=this.userBadges
user=this.user
}} }}
> >
<form class="form-horizontal"> <form class="form-horizontal">

View File

@ -1,100 +1,110 @@
{{#each this.categories as |c|}} <PluginOutlet
<PluginOutlet @name="categories-boxes-wrapper"
@name="category-box-before-each-box" @outletArgs={{hash categories=this.categories}}
@outletArgs={{hash category=c}} >
/> {{#each this.categories as |c|}}
<PluginOutlet
<div @name="category-box-before-each-box"
style={{category-color-variable c.color}} @outletArgs={{hash category=c}}
data-category-id={{c.id}} />
data-notification-level={{c.notificationLevelString}}
data-url={{c.url}}
class="category category-box category-box-{{c.slug}}
{{if c.isMuted 'muted'}}"
>
<div class="category-box-inner">
{{#unless c.isMuted}}
<div class="category-logo">
{{#if c.uploaded_logo.url}}
<CategoryLogo @category={{c}} />
{{/if}}
</div>
{{/unless}}
<div class="category-details">
<div class="category-box-heading">
<a class="parent-box-link" href={{c.url}}>
<h3>
<CategoryTitleBefore @category={{c}} />
{{#if c.read_restricted}}
{{d-icon this.lockIcon}}
{{/if}}
{{c.name}}
</h3>
</a>
</div>
<div
style={{category-color-variable c.color}}
data-category-id={{c.id}}
data-notification-level={{c.notificationLevelString}}
data-url={{c.url}}
class="category category-box category-box-{{c.slug}}
{{if c.isMuted 'muted'}}"
>
<div class="category-box-inner">
{{#unless c.isMuted}} {{#unless c.isMuted}}
<div class="description"> <div class="category-logo">
{{html-safe c.description_excerpt}} {{#if c.uploaded_logo.url}}
<CategoryLogo @category={{c}} />
{{/if}}
</div>
{{/unless}}
<div class="category-details">
<div class="category-box-heading">
<a class="parent-box-link" href={{c.url}}>
<h3>
<CategoryTitleBefore @category={{c}} />
{{#if c.read_restricted}}
{{d-icon this.lockIcon}}
{{/if}}
{{c.name}}
</h3>
</a>
</div> </div>
{{#if c.isGrandParent}} {{#unless c.isMuted}}
{{#each c.subcategories as |subcategory|}} <div class="description">
<div {{html-safe c.description_excerpt}}
data-category-id={{subcategory.id}}
data-notification-level={{subcategory.notificationLevelString}}
style={{border-color subcategory.color}}
class="subcategory with-subcategories
{{if subcategory.uploaded_logo.url 'has-logo' 'no-logo'}}"
>
<div class="subcategory-box-inner">
<CategoryTitleLink @tagName="h4" @category={{subcategory}} />
{{#if subcategory.subcategories}}
<div class="subcategories">
{{#each subcategory.subcategories as |subsubcategory|}}
{{#unless subsubcategory.isMuted}}
<span class="subcategory">
<CategoryTitleBefore @category={{subsubcategory}} />
{{category-link subsubcategory hideParent="true"}}
</span>
{{/unless}}
{{/each}}
</div>
{{/if}}
</div>
</div>
{{/each}}
{{else if c.subcategories}}
<div class="subcategories">
{{#each c.subcategories as |sc|}}
<a class="subcategory" href={{sc.url}}>
<span class="subcategory-image-placeholder">
{{#if sc.uploaded_logo.url}}
<CategoryLogo @category={{sc}} />
{{/if}}
</span>
{{category-link sc hideParent="true"}}
</a>
{{/each}}
</div> </div>
{{/if}}
{{/unless}} {{#if c.isGrandParent}}
{{#each c.subcategories as |subcategory|}}
<div
data-category-id={{subcategory.id}}
data-notification-level={{subcategory.notificationLevelString}}
style={{border-color subcategory.color}}
class="subcategory with-subcategories
{{if subcategory.uploaded_logo.url 'has-logo' 'no-logo'}}"
>
<div class="subcategory-box-inner">
<CategoryTitleLink
@tagName="h4"
@category={{subcategory}}
/>
{{#if subcategory.subcategories}}
<div class="subcategories">
{{#each subcategory.subcategories as |subsubcategory|}}
{{#unless subsubcategory.isMuted}}
<span class="subcategory">
<CategoryTitleBefore
@category={{subsubcategory}}
/>
{{category-link subsubcategory hideParent="true"}}
</span>
{{/unless}}
{{/each}}
</div>
{{/if}}
</div>
</div>
{{/each}}
{{else if c.subcategories}}
<div class="subcategories">
{{#each c.subcategories as |sc|}}
<a class="subcategory" href={{sc.url}}>
<span class="subcategory-image-placeholder">
{{#if sc.uploaded_logo.url}}
<CategoryLogo @category={{sc}} />
{{/if}}
</span>
{{category-link sc hideParent="true"}}
</a>
{{/each}}
</div>
{{/if}}
{{/unless}}
</div>
<PluginOutlet
@name="category-box-below-each-category"
@outletArgs={{hash category=c}}
/>
</div> </div>
<PluginOutlet
@name="category-box-below-each-category"
@outletArgs={{hash category=c}}
/>
</div> </div>
</div>
<PluginOutlet <PluginOutlet
@name="category-box-after-each-box" @name="category-box-after-each-box"
@outletArgs={{hash category=c}} @outletArgs={{hash category=c}}
/> />
{{/each}} {{/each}}
</PluginOutlet>
<PluginOutlet <PluginOutlet
@name="category-boxes-after-boxes" @name="category-boxes-after-boxes"

View File

@ -1,31 +1,22 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { service } from "@ember/service"; import { service } from "@ember/service";
import PluginOutlet from "discourse/components/plugin-outlet";
import avatar from "discourse/helpers/bound-avatar-template"; import avatar from "discourse/helpers/bound-avatar-template";
import { userPath } from "discourse/lib/url"; import { smallUserAttrs } from "discourse/lib/user-list-attrs";
import i18n from "discourse-common/helpers/i18n"; import i18n from "discourse-common/helpers/i18n";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
export default class SmallUserList extends Component { export default class SmallUserList extends Component {
@service currentUser; @service currentUser;
smallUserAtts(user) {
return {
template: user.avatar_template,
username: user.username,
post_url: user.post_url,
url: userPath(user.username_lower),
unknown: user.unknown,
};
}
get users() { get users() {
let users = this.args.data.users; let users = this.args.data.users;
if ( if (
this.args.data.addSelf && this.args.data.addSelf &&
!users.some((u) => u.username === this.currentUser.username) !users.some((u) => u.username === this.currentUser.username)
) { ) {
users = users.concat(this.smallUserAtts(this.currentUser)); users = users.concat(smallUserAttrs(this.currentUser));
} }
return users; return users;
} }
@ -38,38 +29,45 @@ export default class SmallUserList extends Component {
} }
<template> <template>
{{#each this.users as |user|}} <PluginOutlet
{{#if user.unknown}} @name="small-user-list-internal"
<div @outletArgs={{hash data=this.args}}
title={{i18n "post.unknown_user"}} >
class="unknown" {{#each this.users as |user|}}
role="listitem" {{#if user.unknown}}
></div> {{! template-lint-disable require-context-role }}
{{else}} <div
<a title={{i18n "post.unknown_user"}}
class="trigger-user-card" class="unknown"
data-user-card={{user.username}} role="listitem"
title={{user.username}} ></div>
aria-hidden="false" {{else}}
role="listitem" {{! template-lint-disable require-context-role }}
> <a
{{avatar user.template "tiny"}} class="trigger-user-card"
</a> data-user-card={{user.username}}
{{/if}} title={{user.username}}
{{/each}} aria-hidden="false"
role="listitem"
>
{{avatar user.template "tiny"}}
</a>
{{/if}}
{{/each}}
{{#if @data.description}} {{#if @data.description}}
{{#if this.postUrl}} {{#if this.postUrl}}
<a href={{this.postUrl}}> <a href={{this.postUrl}}>
<span aria-hidden="true" class="list-description">
{{i18n @data.description count=@data.count}}
</span>
</a>
{{else}}
<span aria-hidden="true" class="list-description"> <span aria-hidden="true" class="list-description">
{{i18n @data.description count=@data.count}} {{i18n @data.description count=@data.count}}
</span> </span>
</a> {{/if}}
{{else}}
<span aria-hidden="true" class="list-description">
{{i18n @data.description count=@data.count}}
</span>
{{/if}} {{/if}}
{{/if}} </PluginOutlet>
</template> </template>
} }

View File

@ -27,54 +27,48 @@
</div> </div>
{{else}} {{else}}
<div class="card-row first-row"> <div class="card-row first-row">
<div class="user-card-avatar"> <PluginOutlet
{{#if this.contentHidden}} @name="user-card-main-info"
<span class="card-huge-avatar">{{bound-avatar @outletArgs={{hash
this.user user=this.user
"huge" post=this.post
}}</span> contentHidden=this.contentHidden
{{else}} handleShowUser=this.handleShowUser
<a }}
{{on "click" this.handleShowUser}} >
href={{this.user.path}} <div class="user-card-avatar">
class="card-huge-avatar"
>{{bound-avatar this.user "huge"}}</a>
{{/if}}
<UserAvatarFlair @user={{this.user}} />
<div>
<PluginOutlet
@name="user-card-avatar-flair"
@connectorTagName="div"
@outletArgs={{hash user=this.user}}
/>
</div>
</div>
<div class="names">
<div
class="names__primary
{{this.staff}}
{{this.newUser}}
{{if this.nameFirst 'full-name' 'username'}}"
>
{{#if this.contentHidden}} {{#if this.contentHidden}}
<span <span class="card-huge-avatar">{{bound-avatar
id="discourse-user-card-title" this.user
class="name-username-wrapper" "huge"
> }}</span>
{{if
this.nameFirst
this.user.name
(format-username this.user.username)
}}
</span>
{{else}} {{else}}
<a <a
{{on "click" this.handleShowUser}} {{on "click" this.handleShowUser}}
href={{this.user.path}} href={{this.user.path}}
class="user-profile-link" class="card-huge-avatar"
> tabindex="-1"
>{{bound-avatar this.user "huge"}}</a>
{{/if}}
<UserAvatarFlair @user={{this.user}} />
<div>
<PluginOutlet
@name="user-card-avatar-flair"
@connectorTagName="div"
@outletArgs={{hash user=this.user}}
/>
</div>
</div>
<div class="names">
<div
class="names__primary
{{this.staff}}
{{this.newUser}}
{{if this.nameFirst 'full-name' 'username'}}"
>
{{#if this.contentHidden}}
<span <span
id="discourse-user-card-title" id="discourse-user-card-title"
class="name-username-wrapper" class="name-username-wrapper"
@ -85,45 +79,68 @@
(format-username this.user.username) (format-username this.user.username)
}} }}
</span> </span>
{{user-status this.user currentUser=this.currentUser}} {{else}}
</a> <a
{{/if}} {{on "click" this.handleShowUser}}
</div> href={{this.user.path}}
<PluginOutlet class="user-profile-link"
@name="user-card-after-username" aria-label={{i18n
@connectorTagName="div" "user.profile_link"
@outletArgs={{hash user=this.user showUser=this.handleShowUser}} username=this.user.username
/> }}
{{#if this.nameFirst}} >
<div class="names__secondary username">{{this.user.username}}</div> <span
{{else}} id="discourse-user-card-title"
{{#if this.user.name}} class="name-username-wrapper"
<div class="names__secondary full-name">{{this.user.name}}</div> >
{{/if}} {{if
{{/if}} this.nameFirst
{{#if this.user.title}} this.user.name
<div class="names__secondary">{{this.user.title}}</div> (format-username this.user.username)
{{/if}} }}
{{#if this.user.staged}} </span>
<div class="names__secondary staged">{{i18n "user.staged"}}</div> {{user-status this.user currentUser=this.currentUser}}
{{/if}} </a>
{{#if this.hasStatus}} {{/if}}
<div class="user-status">
{{html-safe this.userStatusEmoji}}
<span class="user-status__description">
{{this.user.status.description}}
</span>
{{format-date this.user.status.ends_at format="tiny"}}
</div> </div>
{{/if}}
<div>
<PluginOutlet <PluginOutlet
@name="user-card-post-names" @name="user-card-after-username"
@connectorTagName="div" @connectorTagName="div"
@outletArgs={{hash user=this.user}} @outletArgs={{hash user=this.user showUser=this.handleShowUser}}
/> />
{{#if this.nameFirst}}
<div
class="names__secondary username"
>{{this.user.username}}</div>
{{else}}
{{#if this.user.name}}
<div class="names__secondary full-name">{{this.user.name}}</div>
{{/if}}
{{/if}}
{{#if this.user.title}}
<div class="names__secondary">{{this.user.title}}</div>
{{/if}}
{{#if this.user.staged}}
<div class="names__secondary staged">{{i18n "user.staged"}}</div>
{{/if}}
{{#if this.hasStatus}}
<div class="user-status">
{{html-safe this.userStatusEmoji}}
<span class="user-status__description">
{{this.user.status.description}}
</span>
{{format-date this.user.status.ends_at format="tiny"}}
</div>
{{/if}}
<div>
<PluginOutlet
@name="user-card-post-names"
@connectorTagName="div"
@outletArgs={{hash user=this.user}}
/>
</div>
</div> </div>
</div> </PluginOutlet>
<ul class="usercard-controls"> <ul class="usercard-controls">
{{#if this.user.can_send_private_message_to_user}} {{#if this.user.can_send_private_message_to_user}}
<li class="compose-pm"> <li class="compose-pm">
@ -379,20 +396,25 @@
{{#if this.showBadges}} {{#if this.showBadges}}
<div class="card-row"> <div class="card-row">
{{#if this.user.featured_user_badges}} <PluginOutlet
<div class="badge-section"> @name="user-card-badges"
{{#each this.user.featured_user_badges as |ub|}} @outletArgs={{hash user=this.user post=this.post}}
<UserBadge @badge={{ub.badge}} @user={{this.user}} /> >
{{/each}} {{#if this.user.featured_user_badges}}
{{#if this.showMoreBadges}} <div class="badge-section">
<span class="more-user-badges"> {{#each this.user.featured_user_badges as |ub|}}
<LinkTo @route="user.badges" @model={{this.user}}> <UserBadge @badge={{ub.badge}} @user={{this.user}} />
{{i18n "badges.more_badges" count=this.moreBadgesCount}} {{/each}}
</LinkTo> {{#if this.showMoreBadges}}
</span> <span class="more-user-badges">
{{/if}} <LinkTo @route="user.badges" @model={{this.user}}>
</div> {{i18n "badges.more_badges" count=this.moreBadgesCount}}
{{/if}} </LinkTo>
</span>
{{/if}}
</div>
{{/if}}
</PluginOutlet>
</div> </div>
{{/if}} {{/if}}
{{/if}} {{/if}}

View File

@ -2,4 +2,5 @@ export const VALUE_TRANSFORMERS = Object.freeze([
// use only lowercase names // use only lowercase names
"header-notifications-avatar-size", "header-notifications-avatar-size",
"home-logo-href", "home-logo-href",
"small-user-attrs",
]); ]);

View File

@ -0,0 +1,16 @@
import { applyValueTransformer } from "discourse/lib/transformer";
import { userPath } from "discourse/lib/url";
export function smallUserAttrs(user) {
const defaultAttrs = {
template: user.avatar_template,
username: user.username,
post_url: user.post_url,
url: userPath(user.username_lower),
unknown: user.unknown,
};
return applyValueTransformer("small-user-attrs", defaultAttrs, {
user,
});
}

View File

@ -1,30 +1,40 @@
{{body-class "user-badges-page"}} {{body-class "user-badges-page"}}
<section class="user-content" id="user-content"> <section class="user-content" id="user-content">
<p class="favorite-count"> <PluginOutlet
{{i18n @name="user-badges-content"
"badges.favorite_count" @outletArgs={{hash
count=this.favoriteBadges.length sortedBadges=this.sortedBadges
max=this.siteSettings.max_favorite_badges maxFavBadges=this.siteSettings.max_favorite_badges
favoriteBadges=this.favoriteBadges
canFavoriteMoreBadges=this.canFavoriteMoreBadges
favorite=this.favorite
}} }}
</p> >
<p class="favorite-count">
<div class="badge-group-list"> {{i18n
{{#each this.sortedBadges as |ub|}} "badges.favorite_count"
<BadgeCard count=this.favoriteBadges.length
@badge={{ub.badge}} max=this.siteSettings.max_favorite_badges
@count={{ub.count}} }}
@canFavorite={{ub.can_favorite}} </p>
@isFavorite={{ub.is_favorite}} <div class="badge-group-list">
@username={{this.username}} {{#each this.sortedBadges as |ub|}}
@canFavoriteMoreBadges={{this.canFavoriteMoreBadges}} <BadgeCard
@onFavoriteClick={{action "favorite" ub}} @badge={{ub.badge}}
@filterUser="true" @count={{ub.count}}
@canFavorite={{ub.can_favorite}}
@isFavorite={{ub.is_favorite}}
@username={{this.username}}
@canFavoriteMoreBadges={{this.canFavoriteMoreBadges}}
@onFavoriteClick={{action "favorite" ub}}
@filterUser="true"
/>
{{/each}}
<PluginOutlet
@name="after-user-profile-badges"
@outletArgs={{hash user=this.user.model}}
/> />
{{/each}} </div>
<PluginOutlet </PluginOutlet>
@name="after-user-profile-badges"
@outletArgs={{hash user=this.user.model}}
/>
</div>
</section> </section>

View File

@ -6,7 +6,7 @@ import AdminPostMenu from "discourse/components/admin-post-menu";
import DeleteTopicDisallowedModal from "discourse/components/modal/delete-topic-disallowed"; import DeleteTopicDisallowedModal from "discourse/components/modal/delete-topic-disallowed";
import { formattedReminderTime } from "discourse/lib/bookmark"; import { formattedReminderTime } from "discourse/lib/bookmark";
import { recentlyCopied, showAlert } from "discourse/lib/post-action-feedback"; import { recentlyCopied, showAlert } from "discourse/lib/post-action-feedback";
import { userPath } from "discourse/lib/url"; import { smallUserAttrs } from "discourse/lib/user-list-attrs";
import { import {
NO_REMINDER_ICON, NO_REMINDER_ICON,
WITH_REMINDER_ICON, WITH_REMINDER_ICON,
@ -53,16 +53,6 @@ function registerButton(name, builder) {
_builders[name] = builder; _builders[name] = builder;
} }
function smallUserAtts(user) {
return {
template: user.avatar_template,
username: user.username,
post_url: user.post_url,
url: userPath(user.username_lower),
unknown: user.unknown,
};
}
export function buildButton(name, widget) { export function buildButton(name, widget) {
let { attrs, state, siteSettings, settings, currentUser } = widget; let { attrs, state, siteSettings, settings, currentUser } = widget;
@ -904,14 +894,13 @@ export default createWidget("post-menu", {
getWhoLiked() { getWhoLiked() {
const { attrs, state } = this; const { attrs, state } = this;
return this.store return this.store
.find("post-action-user", { .find("post-action-user", {
id: attrs.id, id: attrs.id,
post_action_type_id: LIKE_ACTION, post_action_type_id: LIKE_ACTION,
}) })
.then((users) => { .then((users) => {
state.likedUsers = users.map(smallUserAtts); state.likedUsers = users.map(smallUserAttrs);
state.total = users.totalRows; state.total = users.totalRows;
}); });
}, },
@ -920,7 +909,7 @@ export default createWidget("post-menu", {
const { attrs, state } = this; const { attrs, state } = this;
return this.store.find("post-reader", { id: attrs.id }).then((users) => { return this.store.find("post-reader", { id: attrs.id }).then((users) => {
state.readers = users.map(smallUserAtts); state.readers = users.map(smallUserAttrs);
state.totalReaders = users.totalRows; state.totalReaders = users.totalRows;
}); });
}, },