From f84982e5cfac49628f757b2b8a127856eb27ee9b Mon Sep 17 00:00:00 2001
From: ChaosExAnima
Date: Wed, 30 Jul 2025 12:40:00 +0200
Subject: [PATCH] Replace DisplayName with new component
---
.../__snapshots__/display_name-test.jsx.snap | 27 ----
.../__tests__/display_name-test.jsx | 19 ---
.../mastodon/components/display_name.tsx | 122 ------------------
.../display_name/display_name.stories.tsx | 69 ++++++++++
.../components/display_name/index.tsx | 87 +++++++++++++
.../mastodon/features/emoji/emoji_html.tsx | 8 +-
.../mastodon/features/emoji/hooks.ts | 31 ++++-
.../mastodon/features/emoji/render.ts | 2 +-
.../styles/mastodon/components.scss | 1 -
9 files changed, 188 insertions(+), 178 deletions(-)
delete mode 100644 app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.jsx.snap
delete mode 100644 app/javascript/mastodon/components/__tests__/display_name-test.jsx
delete mode 100644 app/javascript/mastodon/components/display_name.tsx
create mode 100644 app/javascript/mastodon/components/display_name/display_name.stories.tsx
create mode 100644 app/javascript/mastodon/components/display_name/index.tsx
diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.jsx.snap
deleted file mode 100644
index 9d1b236fad..0000000000
--- a/app/javascript/mastodon/components/__tests__/__snapshots__/display_name-test.jsx.snap
+++ /dev/null
@@ -1,27 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[` > renders display name + account name 1`] = `
-
-
- Foo
",
- }
- }
- />
-
-
-
- @
- bar@baz
-
-
-`;
diff --git a/app/javascript/mastodon/components/__tests__/display_name-test.jsx b/app/javascript/mastodon/components/__tests__/display_name-test.jsx
deleted file mode 100644
index 05a0f47170..0000000000
--- a/app/javascript/mastodon/components/__tests__/display_name-test.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { fromJS } from 'immutable';
-
-import renderer from 'react-test-renderer';
-
-import { DisplayName } from '../display_name';
-
-describe('', () => {
- it('renders display name + account name', () => {
- const account = fromJS({
- username: 'bar',
- acct: 'bar@baz',
- display_name_html: 'Foo
',
- });
- const component = renderer.create();
- const tree = component.toJSON();
-
- expect(tree).toMatchSnapshot();
- });
-});
diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx
deleted file mode 100644
index 8409244827..0000000000
--- a/app/javascript/mastodon/components/display_name.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react';
-
-import type { List } from 'immutable';
-
-import type { Account } from 'mastodon/models/account';
-
-import { autoPlayGif } from '../initial_state';
-
-import { Skeleton } from './skeleton';
-
-interface Props {
- account?: Account;
- others?: List;
- localDomain?: string;
-}
-
-export class DisplayName extends React.PureComponent {
- handleMouseEnter: React.ReactEventHandler = ({
- currentTarget,
- }) => {
- if (autoPlayGif) {
- return;
- }
-
- const emojis =
- currentTarget.querySelectorAll('img.custom-emoji');
-
- emojis.forEach((emoji) => {
- const originalSrc = emoji.getAttribute('data-original');
- if (originalSrc != null) emoji.src = originalSrc;
- });
- };
-
- handleMouseLeave: React.ReactEventHandler = ({
- currentTarget,
- }) => {
- if (autoPlayGif) {
- return;
- }
-
- const emojis =
- currentTarget.querySelectorAll('img.custom-emoji');
-
- emojis.forEach((emoji) => {
- const staticSrc = emoji.getAttribute('data-static');
- if (staticSrc != null) emoji.src = staticSrc;
- });
- };
-
- render() {
- const { others, localDomain } = this.props;
-
- let displayName: React.ReactNode,
- suffix: React.ReactNode,
- account: Account | undefined;
-
- if (others && others.size > 0) {
- account = others.first();
- } else if (this.props.account) {
- account = this.props.account;
- }
-
- if (others && others.size > 1) {
- displayName = others
- .take(2)
- .map((a) => (
-
-
-
- ))
- .reduce((prev, cur) => [prev, ', ', cur]);
-
- if (others.size - 2 > 0) {
- suffix = `+${others.size - 2}`;
- }
- } else if (account) {
- let acct = account.get('acct');
-
- if (!acct.includes('@') && localDomain) {
- acct = `${acct}@${localDomain}`;
- }
-
- displayName = (
-
-
-
- );
- suffix = @{acct};
- } else {
- displayName = (
-
-
-
-
-
- );
- suffix = (
-
-
-
- );
- }
-
- return (
-
- {displayName} {suffix}
-
- );
- }
-}
diff --git a/app/javascript/mastodon/components/display_name/display_name.stories.tsx b/app/javascript/mastodon/components/display_name/display_name.stories.tsx
new file mode 100644
index 0000000000..8d58ebb977
--- /dev/null
+++ b/app/javascript/mastodon/components/display_name/display_name.stories.tsx
@@ -0,0 +1,69 @@
+import type { ComponentProps } from 'react';
+
+import type { Meta, StoryObj } from '@storybook/react-vite';
+
+import { accountFactoryState } from '@/testing/factories';
+
+import { DisplayName } from '.';
+
+type PageProps = Omit, 'account'> & {
+ name: string;
+ username: string;
+ loading: boolean;
+};
+
+const meta = {
+ title: 'Components/DisplayName',
+ args: {
+ username: 'mastodon@mastodon.social',
+ name: 'Test User 🧪',
+ loading: false,
+ simple: false,
+ noDomain: false,
+ localDomain: 'mastodon.social',
+ },
+ tags: [],
+ render({ name, username, loading, ...args }) {
+ const account = !loading
+ ? accountFactoryState({
+ display_name: name,
+ acct: username,
+ })
+ : undefined;
+ return ;
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {},
+};
+
+export const Loading: Story = {
+ args: {
+ loading: true,
+ },
+};
+
+export const NoDomain: Story = {
+ args: {
+ noDomain: true,
+ },
+};
+
+export const Simple: Story = {
+ args: {
+ simple: true,
+ },
+};
+
+export const LocalUser: Story = {
+ args: {
+ username: 'locale',
+ name: 'Local User',
+ localDomain: '',
+ },
+};
diff --git a/app/javascript/mastodon/components/display_name/index.tsx b/app/javascript/mastodon/components/display_name/index.tsx
new file mode 100644
index 0000000000..5cc7efdbec
--- /dev/null
+++ b/app/javascript/mastodon/components/display_name/index.tsx
@@ -0,0 +1,87 @@
+import type { ComponentPropsWithoutRef, FC } from 'react';
+import { useMemo } from 'react';
+
+import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html';
+import type { Account } from '@/mastodon/models/account';
+import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
+
+import { Skeleton } from '../skeleton';
+
+interface Props {
+ account?: Account;
+ localDomain?: string;
+ simple?: boolean;
+ noDomain?: boolean;
+}
+
+export const DisplayName: FC> = ({
+ account,
+ localDomain,
+ simple = false,
+ noDomain = false,
+ className = '',
+ ...props
+}) => {
+ const username = useMemo(() => {
+ if (!account || noDomain) {
+ return null;
+ }
+ let acct = account.get('acct');
+
+ if (!acct.includes('@') && localDomain) {
+ acct = `${acct}@${localDomain}`;
+ }
+ return `@${acct}`;
+ }, [account, localDomain, noDomain]);
+ if (!account) {
+ if (simple) {
+ return null;
+ }
+ return (
+
+
+
+
+
+
+ {!noDomain && (
+
+
+
+ )}
+
+ );
+ }
+ if (simple) {
+ return (
+
+ );
+ }
+ return (
+
+
+
+
+ {username && {username}}
+
+ );
+};
diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx
index fdda62a3e6..0b677cb118 100644
--- a/app/javascript/mastodon/features/emoji/emoji_html.tsx
+++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx
@@ -10,16 +10,22 @@ type EmojiHTMLProps = Omit<
htmlString: string;
extraEmojis?: CustomEmojiMapArg;
as?: Element;
+ shallow?: boolean;
};
export const EmojiHTML = ({
extraEmojis,
htmlString,
as: asElement, // Rename for syntax highlighting
+ shallow,
...props
}: EmojiHTMLProps) => {
const Wrapper = asElement ?? 'div';
- const emojifiedHtml = useEmojify(htmlString, extraEmojis);
+ const emojifiedHtml = useEmojify({
+ text: htmlString,
+ extraEmojis,
+ deep: !shallow,
+ });
if (emojifiedHtml === null) {
return null;
diff --git a/app/javascript/mastodon/features/emoji/hooks.ts b/app/javascript/mastodon/features/emoji/hooks.ts
index 47af37b373..7e91486780 100644
--- a/app/javascript/mastodon/features/emoji/hooks.ts
+++ b/app/javascript/mastodon/features/emoji/hooks.ts
@@ -8,7 +8,7 @@ import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import { toSupportedLocale } from './locale';
import { determineEmojiMode } from './mode';
-import { emojifyElement } from './render';
+import { emojifyElement, emojifyText } from './render';
import type {
CustomEmojiMapArg,
EmojiAppState,
@@ -16,7 +16,17 @@ import type {
} from './types';
import { stringHasAnyEmoji } from './utils';
-export function useEmojify(text: string, extraEmojis?: CustomEmojiMapArg) {
+interface UseEmojifyOptions {
+ text: string;
+ extraEmojis?: CustomEmojiMapArg;
+ deep?: boolean;
+}
+
+export function useEmojify({
+ text,
+ extraEmojis,
+ deep = true,
+}: UseEmojifyOptions) {
const [emojifiedText, setEmojifiedText] = useState(null);
const appState = useEmojiAppState();
@@ -37,16 +47,23 @@ export function useEmojify(text: string, extraEmojis?: CustomEmojiMapArg) {
const emojify = useCallback(
async (input: string) => {
- const wrapper = document.createElement('div');
- wrapper.innerHTML = input;
- const result = await emojifyElement(wrapper, appState, extra);
+ let result: string | null = null;
+ if (deep) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = input;
+ if (await emojifyElement(wrapper, appState, extra)) {
+ result = wrapper.innerHTML;
+ }
+ } else {
+ result = await emojifyText(text, appState, extra);
+ }
if (result) {
- setEmojifiedText(result.innerHTML);
+ setEmojifiedText(result);
} else {
setEmojifiedText(input);
}
},
- [appState, extra],
+ [appState, deep, extra, text],
);
useLayoutEffect(() => {
if (isModernEmojiEnabled() && !!text.trim() && stringHasAnyEmoji(text)) {
diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts
index 6486e65a70..68c4b8964b 100644
--- a/app/javascript/mastodon/features/emoji/render.ts
+++ b/app/javascript/mastodon/features/emoji/render.ts
@@ -103,7 +103,7 @@ export async function emojifyText(
text: string,
appState: EmojiAppState,
extraEmojis: ExtraCustomEmojiMap = {},
-): Promise {
+) {
const cacheKey = createCacheKey(text, appState, extraEmojis);
const cached = getCached(cacheKey);
if (cached !== undefined) {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index d6f0087cc6..2642dff039 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2567,7 +2567,6 @@ a.account__display-name {
}
.display-name {
- display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;