diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 0fbb73083..5f260ccdb 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -1,6 +1,7 @@ import pick from 'lodash.pick'; import * as v from 'valibot'; +import { isDefaultAvatar, isDefaultHeader } from '../utils/accounts'; import { guessFqn } from '../utils/domain'; import { customEmojiSchema } from './custom-emoji'; @@ -30,6 +31,8 @@ const preprocessAccount = v.transform((account: any) => { domain, avatar: account.avatar || account.avatar_static, header: account.header || account.header_static, + avatar_default: isDefaultAvatar(account.avatar || account.avatar_static), + header_default: isDefaultHeader(account.header || account.header_static), local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined, discoverable: account.discoverable || account.pleroma?.source?.discoverable, verified: account.verified || account.pleroma?.tags?.includes('verified'), @@ -187,6 +190,9 @@ const baseAccountSchema = v.object({ pleroma: v.optional(v.any(), undefined), source: v.optional(v.any(), undefined), }), + + avatar_default: v.fallback(v.boolean(), false), + header_default: v.fallback(v.boolean(), false), }); const accountWithMovedAccountSchema = v.object({ diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index 4825da0b6..377f7f94e 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -1,5 +1,6 @@ import * as v from 'valibot'; +import { isDefaultAvatar, isDefaultHeader } from '../utils/accounts'; import { getDomainFromURL } from '../utils/domain'; import { customEmojiSchema } from './custom-emoji'; @@ -35,6 +36,8 @@ const groupSchema = v.pipe(v.any(), v.transform((group: any) => { ...group, avatar: group.avatar || group.avatar_static, header: group.header || group.header_static, + avatar_default: isDefaultAvatar(group.avatar || group.avatar_static), + header_default: isDefaultHeader(group.header || group.header_static), }; }), v.object({ avatar: v.fallback(v.string(), ''), @@ -58,6 +61,9 @@ const groupSchema = v.pipe(v.any(), v.transform((group: any) => { avatar_description: v.fallback(v.string(), ''), header_description: v.fallback(v.string(), ''), + + avatar_default: v.fallback(v.boolean(), false), + header_default: v.fallback(v.boolean(), false), })); /** diff --git a/packages/pl-api/lib/utils/accounts.ts b/packages/pl-api/lib/utils/accounts.ts new file mode 100644 index 000000000..ecf0df8ad --- /dev/null +++ b/packages/pl-api/lib/utils/accounts.ts @@ -0,0 +1,27 @@ +/** Default header filenames from various backends */ +const DEFAULT_HEADERS: string[] = [ + '/assets/default_header.webp', // GoToSocial + '/headers/original/missing.png', // Mastodon + '/api/v1/accounts/identicon', // Mitra + '/images/banner.png', // Pleroma + '/assets/transparent.png', // Iceshrimp.net +]; + +/** Check if the avatar is a default avatar */ +const isDefaultHeader = (url: string = '') => url === '' || DEFAULT_HEADERS.some(header => url.endsWith(header)); + +/** Default avatar filenames from various backends */ +const DEFAULT_AVATARS = [ + ...([1, 2, 3, 4, 5, 6].map(i => `/assets/default_avatars/GoToSocial_icon${i}.webp`)), // GoToSocial + '/avatars/original/missing.png', // Mastodon + '/api/v1/accounts/identicon', // Mitra + '/images/avi.png', // Pleroma +]; + +/** Check if the avatar is a default avatar */ +const isDefaultAvatar = (url: string = '') => url === '' || DEFAULT_AVATARS.some(avatar => url.endsWith(avatar)); + +export { + isDefaultHeader, + isDefaultAvatar, +}; diff --git a/packages/pl-fe/src/components/ui/avatar.tsx b/packages/pl-fe/src/components/ui/avatar.tsx index 08e32b281..3f61584c4 100644 --- a/packages/pl-fe/src/components/ui/avatar.tsx +++ b/packages/pl-fe/src/components/ui/avatar.tsx @@ -5,7 +5,6 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import StillImage, { IStillImage } from 'pl-fe/components/still-image'; import { useSettings } from 'pl-fe/stores/settings'; -import { isDefaultAvatar } from 'pl-fe/utils/accounts'; import AltIndicator from '../alt-indicator'; diff --git a/packages/pl-fe/src/features/account/components/header.tsx b/packages/pl-fe/src/features/account/components/header.tsx index eeed116c5..a6cc9c88b 100644 --- a/packages/pl-fe/src/features/account/components/header.tsx +++ b/packages/pl-fe/src/features/account/components/header.tsx @@ -43,7 +43,6 @@ import { blockDomainMutationOptions, unblockDomainMutationOptions } from 'pl-fe/ import { useModalsActions } from 'pl-fe/stores/modals'; import { useSettings } from 'pl-fe/stores/settings'; import toast from 'pl-fe/toast'; -import { isDefaultHeader } from 'pl-fe/utils/accounts'; import copy from 'pl-fe/utils/copy'; import type { PlfeResponse } from 'pl-fe/api'; @@ -614,7 +613,7 @@ const Header: React.FC = ({ account }) => { let header: React.ReactNode; if (settings.disableUserProvidedMedia) { - if (!account.header_description || isDefaultHeader(account.header)) return null; + if (!account.header_description || account.header_default) return null; else return ( = ({ account }) => { /> ); - if (!isDefaultHeader(account.header)) { + if (!account.header_default) { header = ( {header} diff --git a/packages/pl-fe/src/features/group/components/group-header.tsx b/packages/pl-fe/src/features/group/components/group-header.tsx index aa3f4d9ed..846dea437 100644 --- a/packages/pl-fe/src/features/group/components/group-header.tsx +++ b/packages/pl-fe/src/features/group/components/group-header.tsx @@ -12,7 +12,6 @@ import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import Emojify from 'pl-fe/features/emoji/emojify'; import { useModalsActions } from 'pl-fe/stores/modals'; -import { isDefaultHeader } from 'pl-fe/utils/accounts'; import GroupActionButton from './group-action-button'; import GroupMemberCount from './group-member-count'; @@ -101,7 +100,7 @@ const GroupHeader: React.FC = ({ group }) => { /> ); - if (!isDefaultHeader(group.header)) { + if (!group.header_default) { header = ( {header} diff --git a/packages/pl-fe/src/pages/groups/edit-group.tsx b/packages/pl-fe/src/pages/groups/edit-group.tsx index cf13dc487..da0b043b6 100644 --- a/packages/pl-fe/src/pages/groups/edit-group.tsx +++ b/packages/pl-fe/src/pages/groups/edit-group.tsx @@ -19,12 +19,8 @@ import { useTextField } from 'pl-fe/hooks/forms/use-text-field'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useInstance } from 'pl-fe/hooks/use-instance'; import toast from 'pl-fe/toast'; -import { isDefaultAvatar, isDefaultHeader } from 'pl-fe/utils/accounts'; import { unescapeHTML } from 'pl-fe/utils/html'; -const nonDefaultAvatar = (url: string | undefined) => url && isDefaultAvatar(url) ? undefined : url; -const nonDefaultHeader = (url: string | undefined) => url && isDefaultHeader(url) ? undefined : url; - const messages = defineMessages({ heading: { id: 'navigation_bar.edit_group', defaultMessage: 'Edit Group' }, groupNamePlaceholder: { id: 'manage_group.fields.name_placeholder', defaultMessage: 'Group Name' }, @@ -47,8 +43,8 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { const [isSubmitting, setIsSubmitting] = useState(false); - const avatar = useImageField({ maxPixels: 400 * 400, preview: nonDefaultAvatar(group?.avatar) }); - const header = useImageField({ maxPixels: 1920 * 1080, preview: nonDefaultHeader(group?.header) }); + const avatar = useImageField({ maxPixels: 400 * 400, preview: group?.avatar_default === false ? group.avatar : undefined }); + const header = useImageField({ maxPixels: 1920 * 1080, preview: group?.header_default === false ? group.header : undefined }); const displayName = useTextField(group?.display_name); const note = useTextField(unescapeHTML(group?.note)); diff --git a/packages/pl-fe/src/pages/settings/edit-profile.tsx b/packages/pl-fe/src/pages/settings/edit-profile.tsx index b5d471b47..309f5e6ec 100644 --- a/packages/pl-fe/src/pages/settings/edit-profile.tsx +++ b/packages/pl-fe/src/pages/settings/edit-profile.tsx @@ -28,13 +28,9 @@ import { useFeatures } from 'pl-fe/hooks/use-features'; import { useInstance } from 'pl-fe/hooks/use-instance'; import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; import toast from 'pl-fe/toast'; -import { isDefaultAvatar, isDefaultHeader } from 'pl-fe/utils/accounts'; import type { StreamfieldComponent } from 'pl-fe/components/ui/streamfield'; -const nonDefaultAvatar = (url: string | undefined) => url && isDefaultAvatar(url) ? undefined : url; -const nonDefaultHeader = (url: string | undefined) => url && isDefaultHeader(url) ? undefined : url; - /** * Whether the user is hiding their follows and/or followers. * Pleroma's config is granular, but we simplify it into one setting. @@ -209,8 +205,8 @@ const EditProfilePage: React.FC = () => { const [muteStrangers, setMuteStrangers] = useState(false); const [customCSSEditorExpanded, setCustomCSSEditorExpanded] = useState(false); - const avatar = useImageField({ maxPixels: 400 * 400, preview: nonDefaultAvatar(account?.avatar) }); - const header = useImageField({ maxPixels: 1920 * 1080, preview: nonDefaultHeader(account?.header) }); + const avatar = useImageField({ maxPixels: 400 * 400, preview: account?.avatar_default === false ? account.avatar : undefined }); + const header = useImageField({ maxPixels: 1920 * 1080, preview: account?.header_default === false ? account.header : undefined }); useEffect(() => { client.settings.verifyCredentials().then((credentialAccount) => { diff --git a/packages/pl-fe/src/utils/accounts.ts b/packages/pl-fe/src/utils/accounts.ts index 69e82d718..acd5f1dd4 100644 --- a/packages/pl-fe/src/utils/accounts.ts +++ b/packages/pl-fe/src/utils/accounts.ts @@ -28,35 +28,8 @@ const getAcct = (account: Pick, displayFqn: boolean): s displayFqn === true ? account.fqn : account.acct ); -/** Default header filenames from various backends */ -const DEFAULT_HEADERS: string[] = [ - '/assets/default_header.webp', // GoToSocial - '/headers/original/missing.png', // Mastodon - '/api/v1/accounts/identicon', // Mitra - '/images/banner.png', // Pleroma - '/assets/transparent.png', // Iceshrimp.net - require('pl-fe/assets/images/header-missing.png'), // header not provided by backend -]; - -/** Check if the avatar is a default avatar */ -const isDefaultHeader = (url: string) => url === '' || DEFAULT_HEADERS.some(header => url.endsWith(header)); - -/** Default avatar filenames from various backends */ -const DEFAULT_AVATARS = [ - ...(range(1, 6).map(i => `/assets/default_avatars/GoToSocial_icon${i}.webp`)), // GoToSocial - '/avatars/original/missing.png', // Mastodon - '/api/v1/accounts/identicon', // Mitra - '/images/avi.png', // Pleroma - require('pl-fe/assets/images/avatar-missing.png'), // avatar not provided by backend -]; - -/** Check if the avatar is a default avatar */ -const isDefaultAvatar = (url: string) => url === '' || DEFAULT_AVATARS.some(avatar => url.endsWith(avatar)); - export { getDomain, getBaseURL, getAcct, - isDefaultHeader, - isDefaultAvatar, };