diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts
index 254a5709a..35ee9c76c 100644
--- a/packages/pl-api/lib/entities/account.ts
+++ b/packages/pl-api/lib/entities/account.ts
@@ -33,12 +33,14 @@ const preprocessAccount = v.transform((account: any) => {
if (!account?.acct) return null;
const username = account.username || account.acct.split('@')[0];
- const fqn = guessFqn(account);
+
+ const fqn = account.fqn || guessFqn(account);
+ const domain = fqn.split('@')[1] || '';
return {
username,
fqn,
- domain: fqn.split('@')[1] || '',
+ domain,
avatar_static: account.avatar_static || account.avatar,
header_static: account.header_static || account.header,
local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
@@ -96,7 +98,7 @@ const baseAccountSchema = v.object({
acct: v.fallback(v.string(), ''),
url: v.pipe(v.string(), v.url()),
display_name: v.fallback(v.string(), ''),
- content: v.fallback(v.pipe(v.string(), v.transform((note => note === '
' ? '' : note))), ''),
+ note: v.fallback(v.pipe(v.string(), v.transform(note => note === '' ? '' : note)), ''),
avatar: v.fallback(v.string(), ''),
avatar_static: v.fallback(v.pipe(v.string(), v.url()), ''),
header: v.fallback(v.pipe(v.string(), v.url()), ''),
@@ -110,7 +112,7 @@ const baseAccountSchema = v.object({
noindex: v.fallback(v.nullable(v.boolean()), null),
suspended: v.fallback(v.optional(v.boolean()), undefined),
limited: v.fallback(v.optional(v.boolean()), undefined),
- created_at: v.fallback(datetimeSchema, new Date().toUTCString()),
+ created_at: v.fallback(datetimeSchema, new Date().toISOString()),
last_status_at: v.fallback(v.nullable(v.pipe(v.string(), v.isoDate())), null),
statuses_count: v.fallback(v.number(), 0),
followers_count: v.fallback(v.number(), 0),
diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts
index 58c713bf5..7d5274dc6 100644
--- a/packages/pl-api/lib/entities/admin/account.ts
+++ b/packages/pl-api/lib/entities/admin/account.ts
@@ -7,7 +7,7 @@ import { datetimeSchema, filteredArray } from '../utils';
import { adminIpSchema } from './ip';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Account/} */
-const adminAccountSchema = v.pipe(
+const adminAccountSchema = v.pipe(
v.any(),
v.transform((account: any) => {
if (!account.account) {
diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts
index 5cef6c4a1..93d9bca26 100644
--- a/packages/pl-api/lib/entities/admin/announcement.ts
+++ b/packages/pl-api/lib/entities/admin/announcement.ts
@@ -4,7 +4,7 @@ import * as v from 'valibot';
import { announcementSchema } from '../announcement';
/** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements} */
-const adminAnnouncementSchema = v.pipe(
+const adminAnnouncementSchema = v.pipe(
v.any(),
v.transform((announcement: any) => ({
...announcement,
diff --git a/packages/pl-api/lib/entities/admin/relay.ts b/packages/pl-api/lib/entities/admin/relay.ts
index efe2df0a8..e5d4e6993 100644
--- a/packages/pl-api/lib/entities/admin/relay.ts
+++ b/packages/pl-api/lib/entities/admin/relay.ts
@@ -1,6 +1,6 @@
import * as v from 'valibot';
-const adminRelaySchema = v.pipe(
+const adminRelaySchema = v.pipe(
v.any(),
v.transform((data: any) => ({ id: data.actor, ...data })),
v.object({
diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts
index c3386aec1..6a0526f75 100644
--- a/packages/pl-api/lib/entities/admin/report.ts
+++ b/packages/pl-api/lib/entities/admin/report.ts
@@ -8,7 +8,7 @@ import { datetimeSchema, filteredArray } from '../utils';
import { adminAccountSchema } from './account';
/** @see {@link https://docs.joinmastodon.org/entities/Admin_Report/} */
-const adminReportSchema = v.pipe(
+const adminReportSchema = v.pipe(
v.any(),
v.transform((report: any) => {
if (report.actor) {
diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts
index 6a1413781..22a7fc055 100644
--- a/packages/pl-api/lib/entities/instance.ts
+++ b/packages/pl-api/lib/entities/instance.ts
@@ -203,7 +203,7 @@ const pleromaSchema = coerceObject({
)),
enabled: v.fallback(v.boolean(), false),
}),
- post_formats: v.fallback(v.optional(v.array(v.string())), undefined),
+ post_formats: v.fallback(v.array(v.string()), ['text/plain']),
restrict_unauthenticated: coerceObject({
activities: coerceObject({
local: v.fallback(v.boolean(), false),
diff --git a/packages/pl-api/lib/entities/rule.ts b/packages/pl-api/lib/entities/rule.ts
index a94f2d117..c1077cb0d 100644
--- a/packages/pl-api/lib/entities/rule.ts
+++ b/packages/pl-api/lib/entities/rule.ts
@@ -7,7 +7,7 @@ const baseRuleSchema = v.object({
});
/** @see {@link https://docs.joinmastodon.org/entities/Rule/} */
-const ruleSchema = v.pipe(
+const ruleSchema = v.pipe(
v.any(),
v.transform((data: any) => ({
...data,
diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts
index e27d2d607..483805caf 100644
--- a/packages/pl-api/lib/entities/scrobble.ts
+++ b/packages/pl-api/lib/entities/scrobble.ts
@@ -3,7 +3,7 @@ import * as v from 'valibot';
import { accountSchema } from './account';
import { datetimeSchema } from './utils';
-const scrobbleSchema = v.pipe(
+const scrobbleSchema = v.pipe(
v.any(),
v.transform((scrobble: any) => scrobble ? {
external_link: scrobble.externalLink,
diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts
index 9b9f12168..875c0f8d1 100644
--- a/packages/pl-api/lib/entities/suggestion.ts
+++ b/packages/pl-api/lib/entities/suggestion.ts
@@ -3,7 +3,7 @@ import * as v from 'valibot';
import { accountSchema } from './account';
/** @see {@link https://docs.joinmastodon.org/entities/Suggestion} */
-const suggestionSchema = v.pipe(
+const suggestionSchema = v.pipe(
v.any(),
v.transform((suggestion: any) => {
/**
diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts
index 07ffa270d..ed2727841 100644
--- a/packages/pl-api/lib/entities/translation.ts
+++ b/packages/pl-api/lib/entities/translation.ts
@@ -15,7 +15,7 @@ const translationMediaAttachment = v.object({
});
/** @see {@link https://docs.joinmastodon.org/entities/Translation/} */
-const translationSchema = v.pipe(
+const translationSchema = v.pipe(
v.any(),
v.transform((translation: any) => {
/**
diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts
index 0ed03dbd2..e2909347a 100644
--- a/packages/pl-api/lib/entities/trends-link.ts
+++ b/packages/pl-api/lib/entities/trends-link.ts
@@ -4,7 +4,7 @@ import { blurhashSchema } from './media-attachment';
import { historySchema } from './tag';
/** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */
-const trendsLinkSchema = v.pipe(
+const trendsLinkSchema = v.pipe(
v.any(),
v.transform((link: any) => ({ ...link, id: link.url })),
v.object({
diff --git a/packages/pl-api/lib/features.ts b/packages/pl-api/lib/features.ts
index f1056035f..82058cff3 100644
--- a/packages/pl-api/lib/features.ts
+++ b/packages/pl-api/lib/features.ts
@@ -1000,6 +1000,7 @@ const getFeatures = (instance: Instance) => {
v.software === PLEROMA,
v.software === MITRA,
v.software === GOTOSOCIAL,
+ instance.pleroma.metadata.post_formats.length > 1,
]),
/**
diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json
index f9cdc9b72..6df7d29b5 100644
--- a/packages/pl-api/package.json
+++ b/packages/pl-api/package.json
@@ -1,6 +1,6 @@
{
"name": "pl-api",
- "version": "0.1.2",
+ "version": "0.1.3",
"type": "module",
"homepage": "https://github.com/mkljczk/pl-fe/tree/fork/packages/pl-api",
"repository": {
diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json
index 583511b49..b6ae6c9e4 100644
--- a/packages/pl-fe/package.json
+++ b/packages/pl-fe/package.json
@@ -102,7 +102,7 @@
"mini-css-extract-plugin": "^2.9.1",
"multiselect-react-dropdown": "^2.0.25",
"path-browserify": "^1.0.1",
- "pl-api": "^0.1.2",
+ "pl-api": "^0.1.3",
"pl-hooks": "^0.0.1",
"postcss": "^8.4.47",
"process": "^0.11.10",
diff --git a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts
index c20dc1ce9..6c2c78ff1 100644
--- a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts
+++ b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts
@@ -1,14 +1,13 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import {
adminAnnouncementSchema,
- type AdminAnnouncement as BaseAdminAnnouncement,
+ type AdminAnnouncement,
type AdminCreateAnnouncementParams,
type AdminUpdateAnnouncementParams,
} from 'pl-api';
import * as v from 'valibot';
import { useClient } from 'pl-fe/hooks/useClient';
-import { normalizeAnnouncement, AdminAnnouncement } from 'pl-fe/normalizers/announcement';
import { queryClient } from 'pl-fe/queries/client';
import { useAnnouncements as useUserAnnouncements } from '../announcements/useAnnouncements';
@@ -20,7 +19,7 @@ const useAnnouncements = () => {
const getAnnouncements = async () => {
const data = await client.admin.announcements.getAnnouncements();
- return data.items.map(normalizeAnnouncement);
+ return data.items;
};
const result = useQuery>({
diff --git a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts
index d505fd38c..04443affd 100644
--- a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts
+++ b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts
@@ -1,9 +1,8 @@
import { useMutation, useQuery } from '@tanstack/react-query';
-import { announcementReactionSchema, type AnnouncementReaction } from 'pl-api';
+import { announcementReactionSchema, type AnnouncementReaction, type Announcement } from 'pl-api';
import * as v from 'valibot';
import { useClient } from 'pl-fe/hooks/useClient';
-import { type Announcement, normalizeAnnouncement } from 'pl-fe/normalizers/announcement';
import { queryClient } from 'pl-fe/queries/client';
const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => v.parse(announcementReactionSchema, {
@@ -25,14 +24,9 @@ const updateReactions = (reactions: AnnouncementReaction[], name: string, count:
const useAnnouncements = () => {
const client = useClient();
- const getAnnouncements = async () => {
- const data = await client.announcements.getAnnouncements();
- return data.map(normalizeAnnouncement);
- };
-
const { data, ...result } = useQuery>({
queryKey: ['announcements'],
- queryFn: getAnnouncements,
+ queryFn: () => client.announcements.getAnnouncements(),
placeholderData: [],
});
diff --git a/packages/pl-fe/src/components/account-hover-card.tsx b/packages/pl-fe/src/components/account-hover-card.tsx
index 01b6a22d3..0426fba86 100644
--- a/packages/pl-fe/src/components/account-hover-card.tsx
+++ b/packages/pl-fe/src/components/account-hover-card.tsx
@@ -160,7 +160,7 @@ const AccountHoverCard: React.FC = ({ visible = true }) => {
size='sm'
className='mr-2 rtl:ml-2 rtl:mr-0 [&_br]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate [&_p]:hidden'
>
-
+
)}
diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx
index fcb716158..affda2e66 100644
--- a/packages/pl-fe/src/components/account.tsx
+++ b/packages/pl-fe/src/components/account.tsx
@@ -11,6 +11,7 @@ import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import ActionButton from 'pl-fe/features/ui/components/action-button';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { getAcct } from 'pl-fe/utils/accounts';
@@ -219,8 +220,9 @@ const Account = ({
size='sm'
weight='semibold'
truncate
- dangerouslySetInnerHTML={{ __html: account.display_name_html }}
- />
+ >
+
+
{account.verified && }
@@ -281,8 +283,9 @@ const Account = ({
size='sm'
weight='semibold'
truncate
- dangerouslySetInnerHTML={{ __html: account.display_name_html }}
- />
+ >
+
+
{account.verified && }
@@ -356,7 +359,7 @@ const Account = ({
truncate
size='sm'
>
-
+
)}
diff --git a/packages/pl-fe/src/components/announcements/announcement-content.tsx b/packages/pl-fe/src/components/announcements/announcement-content.tsx
index d9bfb3408..6c405c166 100644
--- a/packages/pl-fe/src/components/announcements/announcement-content.tsx
+++ b/packages/pl-fe/src/components/announcements/announcement-content.tsx
@@ -3,8 +3,9 @@ import { useHistory } from 'react-router-dom';
import { getTextDirection } from 'pl-fe/utils/rtl';
-import type { Mention as MentionEntity } from 'pl-api';
-import type { Announcement } from 'pl-fe/normalizers/announcement';
+import { ParsedContent } from '../parsed-content';
+
+import type { Announcement, Mention as MentionEntity } from 'pl-api';
interface IAnnouncementContent {
announcement: Announcement;
@@ -83,8 +84,9 @@ const AnnouncementContent: React.FC = ({ announcement }) =
dir={direction}
className='text-sm ltr:ml-0 rtl:mr-0'
ref={node}
- dangerouslySetInnerHTML={{ __html: announcement.contentHtml }}
- />
+ >
+
+
);
};
diff --git a/packages/pl-fe/src/components/announcements/announcement.tsx b/packages/pl-fe/src/components/announcements/announcement.tsx
index d83d01feb..c99cb6385 100644
--- a/packages/pl-fe/src/components/announcements/announcement.tsx
+++ b/packages/pl-fe/src/components/announcements/announcement.tsx
@@ -10,8 +10,7 @@ import AnnouncementContent from './announcement-content';
import ReactionsBar from './reactions-bar';
import type { Map as ImmutableMap } from 'immutable';
-import type { CustomEmoji } from 'pl-api';
-import type { Announcement as AnnouncementEntity } from 'pl-fe/normalizers/announcement';
+import type { Announcement as AnnouncementEntity, CustomEmoji } from 'pl-api';
interface IAnnouncement {
announcement: AnnouncementEntity;
diff --git a/packages/pl-fe/src/components/event-preview.tsx b/packages/pl-fe/src/components/event-preview.tsx
index 115d08512..c952cbdb7 100644
--- a/packages/pl-fe/src/components/event-preview.tsx
+++ b/packages/pl-fe/src/components/event-preview.tsx
@@ -8,6 +8,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import EventActionButton from 'pl-fe/features/event/components/event-action-button';
import EventDate from 'pl-fe/features/event/components/event-date';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
@@ -71,7 +72,9 @@ const EventPreview: React.FC = ({ status, className, hideAction,
-
+
+
+
{account.verified && }
diff --git a/packages/pl-fe/src/components/group-card.tsx b/packages/pl-fe/src/components/group-card.tsx
index 66c604e56..1a384169a 100644
--- a/packages/pl-fe/src/components/group-card.tsx
+++ b/packages/pl-fe/src/components/group-card.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import HStack from 'pl-fe/components/ui/hstack';
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 GroupHeaderImage from 'pl-fe/features/group/components/group-header-image';
import GroupMemberCount from 'pl-fe/features/group/components/group-member-count';
import GroupPrivacy from 'pl-fe/features/group/components/group-privacy';
@@ -37,7 +38,9 @@ const GroupCard: React.FC = ({ group }) => (
{/* Group Info */}
-
+
+
+
diff --git a/packages/pl-fe/src/components/groups/popover/group-popover.tsx b/packages/pl-fe/src/components/groups/popover/group-popover.tsx
index 88daf39e8..c2f5f5bed 100644
--- a/packages/pl-fe/src/components/groups/popover/group-popover.tsx
+++ b/packages/pl-fe/src/components/groups/popover/group-popover.tsx
@@ -8,6 +8,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Popover from 'pl-fe/components/ui/popover';
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 GroupMemberCount from 'pl-fe/features/group/components/group-member-count';
import GroupPrivacy from 'pl-fe/features/group/components/group-privacy';
@@ -71,7 +72,9 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
{/* Group Info */}
-
+
+
+
diff --git a/packages/pl-fe/src/components/parsed-content.tsx b/packages/pl-fe/src/components/parsed-content.tsx
index 234a86c85..2573a3c4b 100644
--- a/packages/pl-fe/src/components/parsed-content.tsx
+++ b/packages/pl-fe/src/components/parsed-content.tsx
@@ -3,11 +3,14 @@ import DOMPurify from 'isomorphic-dompurify';
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';
+import Emojify from 'pl-fe/features/emoji/emojify';
+import { makeEmojiMap } from 'pl-fe/utils/normalizers';
+
import HashtagLink from './hashtag-link';
import HoverAccountWrapper from './hover-account-wrapper';
import StatusMention from './status-mention';
-import type { Mention } from 'pl-api';
+import type { CustomEmoji, Mention } from 'pl-api';
const nodesToText = (nodes: Array): string =>
nodes.map(node => node.type === 'text' ? node.data : node.type === 'tag' ? nodesToText(node.children as Array) : '').join('');
@@ -19,14 +22,18 @@ interface IParsedContent {
mentions?: Array;
/** Whether it's a status which has a quote. */
hasQuote?: boolean;
+ /** Related custom emojis. */
+ emojis?: Array;
}
-const ParsedContent: React.FC = (({ html, mentions, hasQuote }) => {
+const ParsedContent: React.FC = (({ html, mentions, hasQuote, emojis }) => {
return useMemo(() => {
if (html.length === 0) {
return null;
}
+ const emojiMap = emojis ? makeEmojiMap(emojis) : undefined;
+
const selectors: Array = [];
// Explicit mentions
@@ -99,6 +106,14 @@ const ParsedContent: React.FC = (({ html, mentions, hasQuote })
return fallback;
}
},
+
+ transform(reactNode) {
+ if (typeof reactNode === 'string') {
+ return ;
+ }
+
+ return reactNode as JSX.Element;
+ },
};
return parse(DOMPurify.sanitize(html, { ADD_ATTR: ['target'], USE_PROFILES: { html: true } }), options);
diff --git a/packages/pl-fe/src/components/polls/poll-footer.tsx b/packages/pl-fe/src/components/polls/poll-footer.tsx
index d619a4bc4..b63aaa4a5 100644
--- a/packages/pl-fe/src/components/polls/poll-footer.tsx
+++ b/packages/pl-fe/src/components/polls/poll-footer.tsx
@@ -12,7 +12,7 @@ import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import RelativeTimestamp from '../relative-timestamp';
import type { Selected } from './poll';
-import type { Poll } from 'pl-fe/normalizers/poll';
+import type { Poll } from 'pl-api';
const messages = defineMessages({
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
diff --git a/packages/pl-fe/src/components/polls/poll-option.tsx b/packages/pl-fe/src/components/polls/poll-option.tsx
index 99cf9c14b..caef4e4fa 100644
--- a/packages/pl-fe/src/components/polls/poll-option.tsx
+++ b/packages/pl-fe/src/components/polls/poll-option.tsx
@@ -7,7 +7,9 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Text from 'pl-fe/components/ui/text';
-import type { Poll } from 'pl-fe/normalizers/poll';
+import { ParsedContent } from '../parsed-content';
+
+import type { Poll } from 'pl-api';
const messages = defineMessages({
voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer' },
@@ -65,8 +67,9 @@ const PollOptionText: React.FC = ({ poll, option, index, active
theme='inherit'
weight='medium'
align='center'
- dangerouslySetInnerHTML={{ __html: option.title_emojified }}
- />
+ >
+
+
@@ -133,9 +136,10 @@ const PollOption: React.FC = (props): JSX.Element | null => {
+ >
+
+
diff --git a/packages/pl-fe/src/components/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx
index ca96e3a67..f4588cfa9 100644
--- a/packages/pl-fe/src/components/status-action-bar.tsx
+++ b/packages/pl-fe/src/components/status-action-bar.tsx
@@ -5,7 +5,7 @@ import { useHistory, useRouteMatch } from 'react-router-dom';
import { blockAccount } from 'pl-fe/actions/accounts';
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'pl-fe/actions/compose';
-import { emojiReact } from 'pl-fe/actions/emoji-reacts';
+import { emojiReact, unEmojiReact } from 'pl-fe/actions/emoji-reacts';
import { editEvent } from 'pl-fe/actions/events';
import { toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog } from 'pl-fe/actions/interactions';
import { deleteStatusModal, toggleStatusSensitivityModal } from 'pl-fe/actions/moderation';
@@ -104,6 +104,7 @@ const messages = defineMessages({
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
viewReactions: { id: 'status.view_reactions', defaultMessage: 'View reactions' },
+ wrench: { id: 'status.wrench', defaultMessage: 'Wrench reaction' },
addKnownLanguage: { id: 'status.add_known_language', defaultMessage: 'Do not auto-translate posts in {language}.' },
translate: { id: 'status.translate', defaultMessage: 'Translate' },
hideTranslation: { id: 'status.hide_translation', defaultMessage: 'Hide translation' },
@@ -143,7 +144,9 @@ const StatusActionBar: React.FC = ({
const { groupRelationship } = useGroupRelationship(status.group_id || undefined);
const features = useFeatures();
const instance = useInstance();
- const { autoTranslate, boostModal, deleteModal, knownLanguages } = useSettings();
+ const { autoTranslate, boostModal, deleteModal, knownLanguages, showWrenchButton } = useSettings();
+
+ const wrenches = showWrenchButton && status.emoji_reactions.find(emoji => emoji.name === '🔧') || undefined;
const { translationLanguages } = useTranslationLanguages();
@@ -153,7 +156,7 @@ const StatusActionBar: React.FC = ({
allow_unauthenticated: allowUnauthenticated,
} = instance.pleroma.metadata.translation;
- const renderTranslate = (me || allowUnauthenticated) && (allowRemote || status.account.local) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && !knownLanguages.includes(status.language);
+ const renderTranslate = (me || allowUnauthenticated) && (allowRemote || status.account.local) && ['public', 'unlisted'].includes(status.visibility) && status.content.length > 0 && status.language !== null && !knownLanguages.includes(status.language);
const supportsLanguages = (translationLanguages[status.language!]?.includes(intl.locale));
return autoTranslate && features.translations && renderTranslate && supportsLanguages;
@@ -211,10 +214,24 @@ const StatusActionBar: React.FC = ({
}
};
+ const handleWrenchClick: React.EventHandler = (e) => {
+ if (!me) {
+ onOpenUnauthorizedModal('DISLIKE');
+ } else if (wrenches?.me) {
+ dispatch(unEmojiReact(status, '🔧'));
+ } else {
+ dispatch(emojiReact(status, '🔧'));
+ }
+ };
+
const handleDislikeLongPress = status.dislikes_count ? () => {
openModal('DISLIKES', { statusId: status.id });
} : undefined;
+ const handleWrenchLongPress = wrenches?.count ? () => {
+ openModal('REACTIONS', { statusId: status.id, reaction: wrenches.name });
+ } : undefined;
+
const handlePickEmoji = (emoji: EmojiType) => {
dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined));
};
@@ -784,6 +801,20 @@ const StatusActionBar: React.FC = ({
/>
)}
+ {me && !withLabels && features.emojiReacts && showWrenchButton && (
+
+ )}
+
{me && !withLabels && features.emojiReacts && (
= React.memo(({
maybeSetOnlyEmoji();
});
- const parsedHtml = useMemo(
+ const content = useMemo(
(): string => translatable && status.translation
? status.translation.content!
- : (status.contentMapHtml && status.currentLanguage)
- ? (status.contentMapHtml[status.currentLanguage] || status.contentHtml)
- : status.contentHtml,
- [status.contentHtml, status.translation, status.currentLanguage],
+ : (status.content_map && status.currentLanguage)
+ ? (status.content_map[status.currentLanguage] || status.content)
+ : status.content,
+ [status.content, status.translation, status.currentLanguage],
);
useEffect(() => {
@@ -121,9 +122,9 @@ const StatusContent: React.FC = React.memo(({
const withSpoiler = status.spoiler_text.length > 0;
- const spoilerText = status.spoilerMapHtml && status.currentLanguage
- ? status.spoilerMapHtml[status.currentLanguage] || status.spoilerHtml
- : status.spoilerHtml;
+ const spoilerText = status.spoiler_text_map && status.currentLanguage
+ ? status.spoiler_text_map[status.currentLanguage] || status.spoiler_text
+ : status.spoiler_text;
const direction = getTextDirection(status.search_index);
const className = clsx('relative text-ellipsis break-words text-gray-900 focus:outline-none dark:text-gray-100', {
@@ -142,11 +143,9 @@ const StatusContent: React.FC = React.memo(({
if (spoilerText) {
output.push(
-
+
+
+
{status.content && expandable && (
)}
diff --git a/packages/pl-fe/src/features/compose/components/content-type-button.tsx b/packages/pl-fe/src/features/compose/components/content-type-button.tsx
index d23f62cf5..bcd40c61a 100644
--- a/packages/pl-fe/src/features/compose/components/content-type-button.tsx
+++ b/packages/pl-fe/src/features/compose/components/content-type-button.tsx
@@ -29,19 +29,27 @@ const ContentTypeButton: React.FC = ({ composeId }) => {
const handleChange = (contentType: string) => () => dispatch(changeComposeContentType(composeId, contentType));
- const options = [
- {
+ const postFormats = instance.pleroma.metadata.post_formats;
+
+ const options = [];
+
+ if (postFormats.includes('text/plain')) {
+ options.push({
icon: require('@tabler/icons/outline/pilcrow.svg'),
text: intl.formatMessage(messages.content_type_plaintext),
value: 'text/plain',
- },
- { icon: require('@tabler/icons/outline/markdown.svg'),
+ });
+ }
+
+ if (postFormats.includes('text/markdown')) {
+ options.push({
+ icon: require('@tabler/icons/outline/markdown.svg'),
text: intl.formatMessage(messages.content_type_markdown),
value: 'text/markdown',
- },
- ];
+ });
+ }
- if (instance.pleroma.metadata.post_formats?.includes('text/html')) {
+ if (postFormats.includes('text/html')) {
options.push({
icon: require('@tabler/icons/outline/html.svg'),
text: intl.formatMessage(messages.content_type_html),
@@ -49,11 +57,13 @@ const ContentTypeButton: React.FC = ({ composeId }) => {
});
}
- options.push({
- icon: require('@tabler/icons/outline/text-caption.svg'),
- text: intl.formatMessage(messages.content_type_wysiwyg),
- value: 'wysiwyg',
- });
+ if (postFormats.includes('text/markdown')) {
+ options.push({
+ icon: require('@tabler/icons/outline/text-caption.svg'),
+ text: intl.formatMessage(messages.content_type_wysiwyg),
+ value: 'wysiwyg',
+ });
+ }
const option = options.find(({ value }) => value === contentType);
diff --git a/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx b/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
index a1a545aa1..5189510a1 100644
--- a/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
+++ b/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
@@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
import Link from 'pl-fe/components/link';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useCompose } from 'pl-fe/hooks/useCompose';
interface IReplyGroupIndicator {
@@ -28,10 +29,11 @@ const ReplyGroupIndicator = (props: IReplyGroupIndicator) => {
id='compose.reply_group_indicator.message'
defaultMessage='Posting to {groupLink}'
values={{
- groupLink: ,
+ groupLink: (
+
+
+
+ ),
}}
/>
diff --git a/packages/pl-fe/src/features/compose/components/reply-indicator.tsx b/packages/pl-fe/src/features/compose/components/reply-indicator.tsx
index 53a0a8edf..8abde19ec 100644
--- a/packages/pl-fe/src/features/compose/components/reply-indicator.tsx
+++ b/packages/pl-fe/src/features/compose/components/reply-indicator.tsx
@@ -12,7 +12,7 @@ import type { Status } from 'pl-fe/normalizers/status';
interface IReplyIndicator {
className?: string;
- status?: Pick;
+ status?: Pick;
onCancel?: () => void;
hideActions: boolean;
}
@@ -52,7 +52,7 @@ const ReplyIndicator: React.FC = ({ className, status, hideActi
size='sm'
direction={getTextDirection(status.search_index)}
>
-
+
{status.media_attachments.length > 0 && (
diff --git a/packages/pl-fe/src/features/directory/components/account-card.tsx b/packages/pl-fe/src/features/directory/components/account-card.tsx
index bfca9ddf1..c31c150be 100644
--- a/packages/pl-fe/src/features/directory/components/account-card.tsx
+++ b/packages/pl-fe/src/features/directory/components/account-card.tsx
@@ -70,13 +70,13 @@ const AccountCard: React.FC = ({ id }) => {
withRelationship={false}
/>
- {!!account.note_emojified && (
+ {!!account.note && (
-
+
)}
diff --git a/packages/pl-fe/src/features/emoji/emojify.tsx b/packages/pl-fe/src/features/emoji/emojify.tsx
new file mode 100644
index 000000000..ac159816a
--- /dev/null
+++ b/packages/pl-fe/src/features/emoji/emojify.tsx
@@ -0,0 +1,103 @@
+import split from 'graphemesplit';
+import React from 'react';
+
+import { makeEmojiMap } from 'pl-fe/utils/normalizers';
+
+import unicodeMapping from './mapping';
+
+import { validEmojiChar } from '.';
+
+import type { CustomEmoji } from 'pl-api';
+
+interface IMaybeEmoji {
+ text: string;
+ emojis: Record;
+}
+
+const MaybeEmoji: React.FC = ({ text, emojis }) => {
+ if (text.length < 3) return text;
+ if (text in emojis) {
+ const emoji = emojis[text];
+ const filename = emoji.static_url;
+
+ if (filename?.length > 0) {
+ return ;
+ }
+ }
+
+ return text;
+};
+
+interface IEmojify {
+ text: string;
+ emojis?: Array | Record;
+}
+
+const Emojify: React.FC = ({ text, emojis = {} }) => React.useMemo(() => {
+ if (Array.isArray(emojis)) emojis = makeEmojiMap(emojis);
+
+ const nodes = [];
+
+ let stack = '';
+ let open = false;
+
+ const clearStack = () => {
+ if (stack.length) nodes.push(stack);
+ open = false;
+ stack = '';
+ };
+
+ for (let c of split(text)) {
+ // convert FE0E selector to FE0F so it can be found in unimap
+ if (c.codePointAt(c.length - 1) === 65038) {
+ c = c.slice(0, -1) + String.fromCodePoint(65039);
+ }
+
+ // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF
+ const unqualified = c + String.fromCodePoint(65039);
+
+ if (c in unicodeMapping) {
+ clearStack();
+
+ const { unified, shortcode } = unicodeMapping[c];
+
+ nodes.push(
+ ,
+ );
+ } else if (unqualified in unicodeMapping) {
+ clearStack();
+
+ const { unified, shortcode } = unicodeMapping[unqualified];
+
+ nodes.push(
+ ,
+ );
+ } else if (c === ':') {
+ if (!open) {
+ clearStack();
+ }
+
+ stack += ':';
+
+ // we see another : we convert it and clear the stack buffer
+ if (open) {
+ nodes.push();
+ stack = '';
+ }
+
+ open = !open;
+ } else {
+ stack += c;
+
+ if (open && !validEmojiChar(c)) {
+ clearStack();
+ }
+ }
+ }
+
+ if (stack.length) nodes.push(stack);
+
+ return nodes;
+}, [text, emojis]);
+
+export { Emojify as default };
diff --git a/packages/pl-fe/src/features/emoji/index.ts b/packages/pl-fe/src/features/emoji/index.ts
index aafe331d6..25a3279d3 100644
--- a/packages/pl-fe/src/features/emoji/index.ts
+++ b/packages/pl-fe/src/features/emoji/index.ts
@@ -1,7 +1,3 @@
-import split from 'graphemesplit';
-
-import unicodeMapping from './mapping';
-
import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from './data';
import type { CustomEmoji as BaseCustomEmoji } from 'pl-api';
@@ -57,148 +53,6 @@ const isAlphaNumeric = (c: string) => {
const validEmojiChar = (c: string) =>
isAlphaNumeric(c) || ['_', '-', '.'].includes(c);
-const convertCustom = (shortname: string, filename: string) =>
- ``;
-
-const convertUnicode = (c: string) => {
- const { unified, shortcode } = unicodeMapping[c];
-
- return ``;
-};
-
-const convertEmoji = (str: string, customEmojis: any) => {
- if (str.length < 3) return str;
- if (str in customEmojis) {
- const emoji = customEmojis[str];
- const filename = emoji.static_url;
-
- if (filename?.length > 0) {
- return convertCustom(str, filename);
- }
- }
-
- return str;
-};
-
-const emojifyText = (str: string, customEmojis = {}) => {
- let buf = '';
- let stack = '';
- let open = false;
-
- const clearStack = () => {
- buf += stack;
- open = false;
- stack = '';
- };
-
- for (let c of split(str)) {
- // convert FE0E selector to FE0F so it can be found in unimap
- if (c.codePointAt(c.length - 1) === 65038) {
- c = c.slice(0, -1) + String.fromCodePoint(65039);
- }
-
- // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF
- const unqualified = c + String.fromCodePoint(65039);
-
- if (c in unicodeMapping) {
- if (open) { // unicode emoji inside colon
- clearStack();
- }
-
- buf += convertUnicode(c);
- } else if (unqualified in unicodeMapping) {
- if (open) { // unicode emoji inside colon
- clearStack();
- }
-
- buf += convertUnicode(unqualified);
- } else if (c === ':') {
- stack += ':';
-
- // we see another : we convert it and clear the stack buffer
- if (open) {
- buf += convertEmoji(stack, customEmojis);
- stack = '';
- }
-
- open = !open;
- } else {
- if (open) {
- stack += c;
-
- // if the stack is non-null and we see invalid chars it's a string not emoji
- // so we push it to the return result and clear it
- if (!validEmojiChar(c)) {
- clearStack();
- }
- } else {
- buf += c;
- }
- }
- }
-
- // never found a closing colon so it's just a raw string
- if (open) {
- buf += stack;
- }
-
- return buf;
-};
-
-const parseHTML = (str: string): { text: boolean; data: string }[] => {
- const tokens = [];
- let buf = '';
- let stack = '';
- let open = false;
-
- for (const c of str) {
- if (c === '<') {
- if (open) {
- tokens.push({ text: true, data: stack });
- stack = '<';
- } else {
- tokens.push({ text: true, data: buf });
- stack = '<';
- open = true;
- }
- } else if (c === '>') {
- if (open) {
- open = false;
- tokens.push({ text: false, data: stack + '>' });
- stack = '';
- buf = '';
- } else {
- buf += '>';
- }
-
- } else {
- if (open) {
- stack += c;
- } else {
- buf += c;
- }
- }
- }
-
- if (open) {
- tokens.push({ text: true, data: buf + stack });
- } else if (buf !== '') {
- tokens.push({ text: true, data: buf });
- }
-
- return tokens;
-};
-
-const emojify = (str: string, customEmojis: Record = {}) =>
- parseHTML(str)
- .map(({ text, data }) => {
- if (!text) return data;
- if (data.length === 0 || data === ' ') return data;
-
- return emojifyText(data, customEmojis);
- })
- .join('');
-
const buildCustomEmojis = (customEmojis: Array) => {
const emojis: EmojiMart[] = [];
@@ -225,5 +79,5 @@ export {
isCustomEmoji,
isNativeEmoji,
buildCustomEmojis,
- emojify as default,
+ validEmojiChar,
};
diff --git a/packages/pl-fe/src/features/event/components/event-header.tsx b/packages/pl-fe/src/features/event/components/event-header.tsx
index a1bc20ba8..07cdde057 100644
--- a/packages/pl-fe/src/features/event/components/event-header.tsx
+++ b/packages/pl-fe/src/features/event/components/event-header.tsx
@@ -19,6 +19,7 @@ import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useFeatures } from 'pl-fe/hooks/useFeatures';
import { useOwnAccount } from 'pl-fe/hooks/useOwnAccount';
@@ -414,7 +415,7 @@ const EventHeader: React.FC = ({ status }) => {
name: (
-
+
{account.verified && }
diff --git a/packages/pl-fe/src/features/event/event-information.tsx b/packages/pl-fe/src/features/event/event-information.tsx
index 2afe02a0d..56766034b 100644
--- a/packages/pl-fe/src/features/event/event-information.tsx
+++ b/packages/pl-fe/src/features/event/event-information.tsx
@@ -162,7 +162,7 @@ const EventInformation: React.FC = ({ params: { statusId: sta
return (
- {!!status.contentHtml.trim() && (
+ {!!status.content.trim() && (
diff --git a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
index ff009530f..30229ff7e 100644
--- a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
+++ b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
@@ -10,6 +10,7 @@ import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
+import Emojify from '../emoji/emojify';
import ActionButton from '../ui/components/action-button';
import { HotKeys } from '../ui/components/hotkeys';
@@ -41,14 +42,9 @@ const SuggestionItem: React.FC = ({ accountId }) => {
-
+
+
+
{account.verified && }
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 d43ccac9c..b520c4896 100644
--- a/packages/pl-fe/src/features/group/components/group-header.tsx
+++ b/packages/pl-fe/src/features/group/components/group-header.tsx
@@ -10,6 +10,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
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 { useModalsStore } from 'pl-fe/stores/modals';
import { isDefaultHeader } from 'pl-fe/utils/accounts';
@@ -141,9 +142,10 @@ const GroupHeader: React.FC = ({ group }) => {
+ >
+
+
@@ -157,7 +159,7 @@ const GroupHeader: React.FC = ({ group }) => {
align='center'
className='[&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue'
>
-
+
diff --git a/packages/pl-fe/src/features/group/edit-group.tsx b/packages/pl-fe/src/features/group/edit-group.tsx
index 2ea0a1be0..e7c4ca078 100644
--- a/packages/pl-fe/src/features/group/edit-group.tsx
+++ b/packages/pl-fe/src/features/group/edit-group.tsx
@@ -18,6 +18,7 @@ import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { useInstance } from 'pl-fe/hooks/useInstance';
import toast from 'pl-fe/toast';
import { isDefaultAvatar, isDefaultHeader } from 'pl-fe/utils/accounts';
+import { unescapeHTML } from 'pl-fe/utils/html';
import AvatarPicker from '../edit-profile/components/avatar-picker';
import HeaderPicker from '../edit-profile/components/header-picker';
@@ -51,7 +52,7 @@ const EditGroup: React.FC = ({ params: { groupId } }) => {
const header = useImageField({ maxPixels: 1920 * 1080, preview: nonDefaultHeader(group?.header) });
const displayName = useTextField(group?.display_name);
- const note = useTextField(group?.note_plain);
+ const note = useTextField(unescapeHTML(group?.note));
const maxName = Number(instance.configuration.groups.max_characters_name);
const maxNote = Number(instance.configuration.groups.max_characters_description);
diff --git a/packages/pl-fe/src/features/group/manage-group.tsx b/packages/pl-fe/src/features/group/manage-group.tsx
index aa63d4bcb..de8637df6 100644
--- a/packages/pl-fe/src/features/group/manage-group.tsx
+++ b/packages/pl-fe/src/features/group/manage-group.tsx
@@ -13,6 +13,7 @@ import Text from 'pl-fe/components/ui/text';
import { useModalsStore } from 'pl-fe/stores/modals';
import toast from 'pl-fe/toast';
+import Emojify from '../emoji/emojify';
import ColumnForbidden from '../ui/components/column-forbidden';
type RouteParams = { groupId: string };
@@ -86,7 +87,7 @@ const ManageGroup: React.FC = ({ params }) => {
-
+
>
diff --git a/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx b/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
index 6201f591d..88e5f540b 100644
--- a/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
+++ b/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
@@ -7,13 +7,14 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
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 GroupActionButton from 'pl-fe/features/group/components/group-action-button';
import { shortNumberFormat } from 'pl-fe/utils/numbers';
import type { Group } from 'pl-fe/normalizers/group';
interface IGroupListItem {
- group: Pick;
+ group: Pick;
withJoinAction?: boolean;
}
@@ -34,11 +35,9 @@ const GroupListItem = (props: IGroupListItem) => {
/>
-
+
+
+
{
const instance = useInstance();
- const description = useMemo(() => DOMPurify.sanitize(instance.description), [instance.description]);
return (
@@ -21,9 +20,10 @@ const SiteBanner: React.FC = () => {
{instance.description.trim().length > 0 && (
+ direction={getTextDirection(instance.description)}
+ >
+
+
)}
);
diff --git a/packages/pl-fe/src/features/notifications/components/notification.tsx b/packages/pl-fe/src/features/notifications/components/notification.tsx
index 63b0834b4..462fa92da 100644
--- a/packages/pl-fe/src/features/notifications/components/notification.tsx
+++ b/packages/pl-fe/src/features/notifications/components/notification.tsx
@@ -14,6 +14,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Text from 'pl-fe/components/ui/text';
import AccountContainer from 'pl-fe/containers/account-container';
import StatusContainer from 'pl-fe/containers/status-container';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useInstance } from 'pl-fe/hooks/useInstance';
@@ -35,14 +36,15 @@ const notificationForScreenReader = (intl: IntlShape, message: string, timestamp
return output.join(', ');
};
-const buildLink = (account: Pick): JSX.Element => (
+const buildLink = (account: Pick): JSX.Element => (
+ >
+
+
);
@@ -151,7 +153,7 @@ const messages: Record = defineMe
const buildMessage = (
intl: IntlShape,
type: NotificationType | 'reply',
- accounts: Array>,
+ accounts: Array>,
targetName: string,
instanceTitle: string,
): React.ReactNode => {
diff --git a/packages/pl-fe/src/features/preferences/index.tsx b/packages/pl-fe/src/features/preferences/index.tsx
index a55057a30..8c8f635c1 100644
--- a/packages/pl-fe/src/features/preferences/index.tsx
+++ b/packages/pl-fe/src/features/preferences/index.tsx
@@ -9,6 +9,7 @@ import { Mutliselect, SelectDropdown } from 'pl-fe/features/forms';
import SettingToggle from 'pl-fe/features/notifications/components/setting-toggle';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useFeatures } from 'pl-fe/hooks/useFeatures';
+import { useInstance } from 'pl-fe/hooks/useInstance';
import { useSettings } from 'pl-fe/hooks/useSettings';
import ThemeToggle from '../ui/components/theme-toggle';
@@ -98,6 +99,7 @@ const Preferences = () => {
const dispatch = useAppDispatch();
const features = useFeatures();
const settings = useSettings();
+ const instance = useInstance();
const onSelectChange = (event: React.ChangeEvent, path: string[]) => {
dispatch(changeSetting(path, event.target.value, { showAlert: true }));
@@ -123,11 +125,17 @@ const Preferences = () => {
private: intl.formatMessage(messages.privacy_followers_only),
}), [settings.locale]);
- const defaultContentTypeOptions = React.useMemo(() => ({
- 'text/plain': intl.formatMessage(messages.content_type_plaintext),
- 'text/markdown': intl.formatMessage(messages.content_type_markdown),
- 'text/html': intl.formatMessage(messages.content_type_html),
- }), [settings.locale]);
+ const defaultContentTypeOptions = React.useMemo(() => {
+ const postFormats = instance.pleroma.metadata.post_formats;
+
+ const options = Object.entries({
+ 'text/plain': intl.formatMessage(messages.content_type_plaintext),
+ 'text/markdown': intl.formatMessage(messages.content_type_markdown),
+ 'text/html': intl.formatMessage(messages.content_type_html),
+ }).filter(([key]) => postFormats.includes(key));
+
+ if (options.length > 1) return Object.fromEntries(options);
+ }, [settings.locale]);
return (