Fixes display names not rendering with emojis (#37251)

This commit is contained in:
Echo 2025-12-15 13:54:46 +01:00 committed by GitHub
parent 2984f377e8
commit 5fa7654688
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 36 additions and 29 deletions

View File

@ -2,15 +2,19 @@
module WrapstodonHelper module WrapstodonHelper
def render_wrapstodon_share_data(report) def render_wrapstodon_share_data(report)
json = ActiveModelSerializers::SerializableResource.new( payload = ActiveModelSerializers::SerializableResource.new(
AnnualReportsPresenter.new([report]), AnnualReportsPresenter.new([report]),
serializer: REST::AnnualReportsSerializer, serializer: REST::AnnualReportsSerializer,
scope: nil, scope: nil,
scope_name: :current_user scope_name: :current_user
).to_json ).as_json
payload[:me] = current_account.id.to_s if user_signed_in?
json_string = payload.to_json
# rubocop:disable Rails/OutputSafety # rubocop:disable Rails/OutputSafety
content_tag(:script, json_escape(json).html_safe, type: 'application/json', id: 'wrapstodon-data') content_tag(:script, json_escape(json_string).html_safe, type: 'application/json', id: 'wrapstodon-data')
# rubocop:enable Rails/OutputSafety # rubocop:enable Rails/OutputSafety
end end
end end

View File

@ -25,7 +25,7 @@ function loaded() {
const initialState = JSON.parse( const initialState = JSON.parse(
propsNode.textContent, propsNode.textContent,
) as ApiAnnualReportResponse; ) as ApiAnnualReportResponse & { me?: string };
const report = initialState.annual_reports[0]; const report = initialState.annual_reports[0];
if (!report) { if (!report) {
@ -35,7 +35,10 @@ function loaded() {
// Set up store // Set up store
store.dispatch( store.dispatch(
hydrateStore({ hydrateStore({
meta: { locale: document.documentElement.lang }, meta: {
locale: document.documentElement.lang,
me: initialState.me,
},
accounts: initialState.accounts, accounts: initialState.accounts,
}), }),
); );

View File

@ -12,6 +12,7 @@ import replier from '@/images/archetypes/replier.png';
import space_elements from '@/images/archetypes/space_elements.png'; import space_elements from '@/images/archetypes/space_elements.png';
import { Avatar } from '@/mastodon/components/avatar'; import { Avatar } from '@/mastodon/components/avatar';
import { Button } from '@/mastodon/components/button'; import { Button } from '@/mastodon/components/button';
import { DisplayName } from '@/mastodon/components/display_name';
import { me } from '@/mastodon/initial_state'; import { me } from '@/mastodon/initial_state';
import type { Account } from '@/mastodon/models/account'; import type { Account } from '@/mastodon/models/account';
import type { import type {
@ -137,9 +138,6 @@ export const Archetype: React.FC<{
? archetypeSelfDescriptions ? archetypeSelfDescriptions
: archetypePublicDescriptions; : archetypePublicDescriptions;
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we specifically want to fallback if `display_name` is empty
const name = account?.display_name || account?.username;
return ( return (
<div <div
className={classNames(styles.box, styles.archetype)} className={classNames(styles.box, styles.archetype)}
@ -182,7 +180,9 @@ export const Archetype: React.FC<{
<FormattedMessage <FormattedMessage
id='annual_report.summary.archetype.title_public' id='annual_report.summary.archetype.title_public'
defaultMessage="{name}'s archetype" defaultMessage="{name}'s archetype"
values={{ name }} values={{
name: <DisplayName variant='simple' account={account} />,
}}
/> />
)} )}
</h2> </h2>
@ -199,7 +199,7 @@ export const Archetype: React.FC<{
<p> <p>
{isRevealed ? ( {isRevealed ? (
intl.formatMessage(descriptions[archetype], { intl.formatMessage(descriptions[archetype], {
name, name: <DisplayName variant='simple' account={account} />,
}) })
) : ( ) : (
<FormattedMessage <FormattedMessage

View File

@ -10,7 +10,6 @@ import classNames from 'classnames/bind';
import { closeModal } from '@/mastodon/actions/modal'; import { closeModal } from '@/mastodon/actions/modal';
import { IconButton } from '@/mastodon/components/icon_button'; import { IconButton } from '@/mastodon/components/icon_button';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { me } from '@/mastodon/initial_state';
import { import {
createAppSelector, createAppSelector,
useAppDispatch, useAppDispatch,
@ -30,9 +29,6 @@ const moduleClassNames = classNames.bind(styles);
const accountSelector = createAppSelector( const accountSelector = createAppSelector(
[(state) => state.accounts, (state) => state.annualReport.report], [(state) => state.accounts, (state) => state.annualReport.report],
(accounts, report) => { (accounts, report) => {
if (me) {
return accounts.get(me);
}
if (report?.schema_version === 2) { if (report?.schema_version === 2) {
return accounts.get(report.account_id); return accounts.get(report.account_id);
} }
@ -109,7 +105,7 @@ export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
{topHashtag && ( {topHashtag && (
<MostUsedHashtag <MostUsedHashtag
hashtag={topHashtag} hashtag={topHashtag}
name={account?.display_name} account={account}
context={context} context={context}
/> />
)} )}

View File

@ -2,15 +2,17 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { DisplayName } from '@/mastodon/components/display_name';
import type { Account } from '@/mastodon/models/account';
import type { NameAndCount } from 'mastodon/models/annual_report'; import type { NameAndCount } from 'mastodon/models/annual_report';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const MostUsedHashtag: React.FC<{ export const MostUsedHashtag: React.FC<{
hashtag: NameAndCount; hashtag: NameAndCount;
name: string | undefined;
context: 'modal' | 'standalone'; context: 'modal' | 'standalone';
}> = ({ hashtag, name, context }) => { account?: Account;
}> = ({ hashtag, context, account }) => {
return ( return (
<div <div
className={classNames(styles.box, styles.mostUsedHashtag, styles.content)} className={classNames(styles.box, styles.mostUsedHashtag, styles.content)}
@ -25,20 +27,22 @@ export const MostUsedHashtag: React.FC<{
<div className={styles.statExtraLarge}>#{hashtag.name}</div> <div className={styles.statExtraLarge}>#{hashtag.name}</div>
<p> <p>
{context === 'modal' ? ( {context === 'modal' && (
<FormattedMessage <FormattedMessage
id='annual_report.summary.most_used_hashtag.used_count' id='annual_report.summary.most_used_hashtag.used_count'
defaultMessage='You included this hashtag in {count, plural, one {one post} other {# posts}}.' defaultMessage='You included this hashtag in {count, plural, one {one post} other {# posts}}.'
values={{ count: hashtag.count }} values={{ count: hashtag.count }}
/> />
) : ( )}
name && ( {context !== 'modal' && account && (
<FormattedMessage <FormattedMessage
id='annual_report.summary.most_used_hashtag.used_count_public' id='annual_report.summary.most_used_hashtag.used_count_public'
defaultMessage='{name} included this hashtag in {count, plural, one {one post} other {# posts}}.' defaultMessage='{name} included this hashtag in {count, plural, one {one post} other {# posts}}.'
values={{ count: hashtag.count, name }} values={{
/> count: hashtag.count,
) name: <DisplayName variant='simple' account={account} />,
}}
/>
)} )}
</p> </p>
</div> </div>

View File

@ -3,12 +3,13 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { IconLogo } from '@/mastodon/components/logo'; import { IconLogo } from '@/mastodon/components/logo';
import { me } from '@/mastodon/initial_state'; import { useAppSelector } from '@/mastodon/store';
import { AnnualReport } from './index'; import { AnnualReport } from './index';
import classes from './shared_page.module.scss'; import classes from './shared_page.module.scss';
export const WrapstodonSharedPage: FC = () => { export const WrapstodonSharedPage: FC = () => {
const isLoggedIn = useAppSelector((state) => !!state.meta.get('me'));
return ( return (
<main className={classes.wrapper}> <main className={classes.wrapper}>
<AnnualReport /> <AnnualReport />
@ -23,7 +24,7 @@ export const WrapstodonSharedPage: FC = () => {
<a href='https://joinmastodon.org'> <a href='https://joinmastodon.org'>
<FormattedMessage id='footer.about' defaultMessage='About' /> <FormattedMessage id='footer.about' defaultMessage='About' />
</a> </a>
{!me && ( {!isLoggedIn && (
<a href='https://joinmastodon.org/servers'> <a href='https://joinmastodon.org/servers'>
<FormattedMessage <FormattedMessage
id='annual_report.shared_page.sign_up' id='annual_report.shared_page.sign_up'

View File

@ -11,7 +11,6 @@
= render 'og_description', account: @account = render 'og_description', account: @account
= render 'og_image', report: @generated_annual_report = render 'og_image', report: @generated_annual_report
= render_initial_state
= vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous' = vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous'
- content_for :html_classes, 'theme-dark' - content_for :html_classes, 'theme-dark'