kopia lustrzana https://github.com/cloudflare/wildebeest
Merge pull request #287 from cloudflare/custom-emojis-frontend
show custom emojis in display namespull/288/head
commit
c353bca4ff
|
@ -6,6 +6,7 @@ import type { Account, MastodonStatus } from '~/types'
|
||||||
import styles from '../../utils/innerHtmlContent.scss?inline'
|
import styles from '../../utils/innerHtmlContent.scss?inline'
|
||||||
import { MediaGallery } from '../MediaGallery.tsx'
|
import { MediaGallery } from '../MediaGallery.tsx'
|
||||||
import { useAccountUrl } from '~/utils/useAccountUrl'
|
import { useAccountUrl } from '~/utils/useAccountUrl'
|
||||||
|
import { getDisplayNameElement } from '~/utils/getDisplayNameElement'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: MastodonStatus
|
status: MastodonStatus
|
||||||
|
@ -33,7 +34,7 @@ export default component$((props: Props) => {
|
||||||
<div class="flex-col ml-3">
|
<div class="flex-col ml-3">
|
||||||
<div>
|
<div>
|
||||||
<Link class="no-underline" href={accountUrl}>
|
<Link class="no-underline" href={accountUrl}>
|
||||||
{status.account.display_name}
|
{getDisplayNameElement(status.account)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-wildebeest-500">@{status.account.username}</div>
|
<div class="text-wildebeest-500">@{status.account.username}</div>
|
||||||
|
@ -75,7 +76,7 @@ export const RebloggerLink = component$(({ account }: { account: Account | null
|
||||||
<p>
|
<p>
|
||||||
<i class="fa fa-retweet mr-3 w-4 inline-block" />
|
<i class="fa fa-retweet mr-3 w-4 inline-block" />
|
||||||
<a class="no-underline" href={accountUrl}>
|
<a class="no-underline" href={accountUrl}>
|
||||||
{account.display_name}
|
{getDisplayNameElement(account)}
|
||||||
</a>
|
</a>
|
||||||
boosted
|
boosted
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import styles from '../../../../utils/innerHtmlContent.scss?inline'
|
||||||
import { getTextContent } from 'wildebeest/backend/src/activitypub/objects'
|
import { getTextContent } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { getDocumentHead } from '~/utils/getDocumentHead'
|
import { getDocumentHead } from '~/utils/getDocumentHead'
|
||||||
import { useAccountUrl } from '~/utils/useAccountUrl'
|
import { useAccountUrl } from '~/utils/useAccountUrl'
|
||||||
|
import { getDisplayNameElement } from '~/utils/getDisplayNameElement'
|
||||||
|
|
||||||
export const statusLoader = loader$<
|
export const statusLoader = loader$<
|
||||||
{ DATABASE: D1Database },
|
{ DATABASE: D1Database },
|
||||||
|
@ -81,7 +82,7 @@ export const AccountCard = component$<{ status: MastodonStatus }>(({ status }) =
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<Link href={accountUrl} class="no-underline">
|
<Link href={accountUrl} class="no-underline">
|
||||||
{status.account.display_name}
|
{getDisplayNameElement(status.account)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 text-wildebeest-400">@{status.account.acct}</div>
|
<div class="p-1 text-wildebeest-400">@{status.account.acct}</div>
|
||||||
|
|
|
@ -9,10 +9,11 @@ import { getAccount } from 'wildebeest/backend/src/accounts/getAccount'
|
||||||
import { getNotFoundHtml } from '~/utils/getNotFoundHtml/getNotFoundHtml'
|
import { getNotFoundHtml } from '~/utils/getNotFoundHtml/getNotFoundHtml'
|
||||||
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
|
||||||
import { getDocumentHead } from '~/utils/getDocumentHead'
|
import { getDocumentHead } from '~/utils/getDocumentHead'
|
||||||
import type { MastodonStatus } from '~/types'
|
import type { Account, MastodonStatus } from '~/types'
|
||||||
import { StatusesPanel } from '~/components/StatusesPanel/StatusesPanel'
|
import { StatusesPanel } from '~/components/StatusesPanel/StatusesPanel'
|
||||||
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
|
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
|
||||||
import { getLocalStatuses } from 'wildebeest/functions/api/v1/accounts/[id]/statuses'
|
import { getLocalStatuses } from 'wildebeest/functions/api/v1/accounts/[id]/statuses'
|
||||||
|
import { getDisplayNameElement } from '~/utils/getDisplayNameElement'
|
||||||
|
|
||||||
export const accountLoader = loader$<
|
export const accountLoader = loader$<
|
||||||
{ DATABASE: D1Database },
|
{ DATABASE: D1Database },
|
||||||
|
@ -93,7 +94,7 @@ export default component$(() => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-5">
|
<div class="px-5">
|
||||||
<h2 class="font-bold">{accountDetails.account.display_name}</h2>
|
<h2 class="font-bold">{getDisplayNameElement(accountDetails.account as Account)}</h2>
|
||||||
<span class="block my-1 text-wildebeest-400">{accountDetails.accountHandle}</span>
|
<span class="block my-1 text-wildebeest-400">{accountDetails.accountHandle}</span>
|
||||||
<div class="inner-html-content my-5" dangerouslySetInnerHTML={accountDetails.account.note} />
|
<div class="inner-html-content my-5" dangerouslySetInnerHTML={accountDetails.account.note} />
|
||||||
<dl class="mb-6 flex flex-col bg-wildebeest-800 border border-wildebeest-600 rounded-md">
|
<dl class="mb-6 flex flex-col bg-wildebeest-800 border border-wildebeest-600 rounded-md">
|
||||||
|
|
|
@ -65,3 +65,14 @@ button {
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-emoji {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: inherit;
|
||||||
|
vertical-align: middle;
|
||||||
|
-o-object-fit: contain;
|
||||||
|
object-fit: contain;
|
||||||
|
margin: -.2ex 0.15em .2ex;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { type JSXNode } from '@builder.io/qwik'
|
||||||
|
import { type Account } from '~/types'
|
||||||
|
|
||||||
|
export function getDisplayNameElement(account: Account): JSXNode {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{account.display_name.split(/(:[^\s:]+:)/g).map((str) => {
|
||||||
|
const customEmojiMatch = str.match(/^:([^\s:]+):$/)
|
||||||
|
if (customEmojiMatch) {
|
||||||
|
const shortCode = customEmojiMatch[1]
|
||||||
|
const customEmojiInfo = account.emojis.find((emoji) => emoji.shortcode === shortCode)
|
||||||
|
if (customEmojiInfo) {
|
||||||
|
// eslint-disable-next-line qwik/single-jsx-root
|
||||||
|
return <img class="custom-emoji" src={customEmojiInfo.url} alt={`:${shortCode}:`}></img>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <>{str}</>
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('View of custom emojis in an toots author display name', async ({ page, browserName }) => {
|
||||||
|
// this page.route is a hack to mock the custom emojis since they haven't
|
||||||
|
// yet been implemented in the backend (this should be not needed and removed
|
||||||
|
// when those are implemented)
|
||||||
|
test.skip(
|
||||||
|
browserName !== 'chromium',
|
||||||
|
"Only chromium seem to test this well, I suspect it's because of the page.route"
|
||||||
|
)
|
||||||
|
await page.route('http://127.0.0.1:8788/@george/*/q-data.json', async (route) => {
|
||||||
|
const response = await route.fetch()
|
||||||
|
let body = await response.text()
|
||||||
|
body = body.replace(
|
||||||
|
/"emojis":\[\]/g,
|
||||||
|
`"emojis": ${JSON.stringify([
|
||||||
|
{
|
||||||
|
shortcode: 'verified',
|
||||||
|
url: 'https://files.mastodon.social/cache/custom_emojis/images/000/452/462/original/947cae7ac4dfdfa0.png',
|
||||||
|
static_url:
|
||||||
|
'https://files.mastodon.social/cache/custom_emojis/images/000/452/462/static/947cae7ac4dfdfa0.png',
|
||||||
|
visible_in_picker: true,
|
||||||
|
},
|
||||||
|
])}`
|
||||||
|
)
|
||||||
|
await route.fulfill({
|
||||||
|
response,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto('http://127.0.0.1:8788/explore')
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator('article')
|
||||||
|
.filter({ hasText: 'George' })
|
||||||
|
.filter({
|
||||||
|
hasText: 'We did it!',
|
||||||
|
})
|
||||||
|
.locator('i.fa-globe + span')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
const customEmojiLocator = page.getByRole('link', { name: 'George :verified: 👍', exact: true }).getByRole('img')
|
||||||
|
await expect(customEmojiLocator).toBeVisible()
|
||||||
|
await expect(customEmojiLocator).toHaveAttribute(
|
||||||
|
'src',
|
||||||
|
'https://files.mastodon.social/cache/custom_emojis/images/000/452/462/original/947cae7ac4dfdfa0.png'
|
||||||
|
)
|
||||||
|
})
|
|
@ -84,7 +84,7 @@ export function getMockStatusFn(): () => MastodonStatus {
|
||||||
const paddedNum = `${numOfGeneratedMockStatuses}`.padStart(3, '0')
|
const paddedNum = `${numOfGeneratedMockStatuses}`.padStart(3, '0')
|
||||||
const status = {
|
const status = {
|
||||||
content: `Mock Fetched Status #${paddedNum}`,
|
content: `Mock Fetched Status #${paddedNum}`,
|
||||||
account: {} as Account,
|
account: { display_name: 'test', emojis: [] } as unknown as Account,
|
||||||
media_attachments: [],
|
media_attachments: [],
|
||||||
} as unknown as MastodonStatus
|
} as unknown as MastodonStatus
|
||||||
numOfGeneratedMockStatuses++
|
numOfGeneratedMockStatuses++
|
||||||
|
|
Ładowanie…
Reference in New Issue