From 61880b8b865e71dfd4f3bdca4a3d99ba5c278d18 Mon Sep 17 00:00:00 2001 From: tassoman Date: Mon, 21 Oct 2024 19:44:36 +0200 Subject: [PATCH 01/12] Show App used for posting a status, when available --- .../src/features/status/components/detailed-status.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/pl-fe/src/features/status/components/detailed-status.tsx b/packages/pl-fe/src/features/status/components/detailed-status.tsx index 71854c6cb..74f694291 100644 --- a/packages/pl-fe/src/features/status/components/detailed-status.tsx +++ b/packages/pl-fe/src/features/status/components/detailed-status.tsx @@ -150,6 +150,14 @@ const DetailedStatus: React.FC = ({ + {actualStatus.application && ( + + + ({actualStatus.application.name}) + + + )} + {actualStatus.edited_at && ( <> {' 路 '} From 47c190cd16bd905e8319e7c932b6d6444047f7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 20:35:52 +0200 Subject: [PATCH 02/12] Fix chats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-fe/src/queries/chats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pl-fe/src/queries/chats.ts b/packages/pl-fe/src/queries/chats.ts index 1ad2bb1f3..e617ab771 100644 --- a/packages/pl-fe/src/queries/chats.ts +++ b/packages/pl-fe/src/queries/chats.ts @@ -181,7 +181,7 @@ const useChatActions = (chatId: string) => { chat_id: variables.chatId, content: variables.content, id: pendingId, - created_at: new Date(), + created_at: new Date().toISOString(), account_id: account?.id, unread: true, }), From c618e1f6197ac7528e054bc764edf5f2f326493c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 20:39:23 +0200 Subject: [PATCH 03/12] Default to whatever content type is supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-api/lib/entities/account.ts | 2 +- packages/pl-api/lib/entities/admin/account.ts | 2 +- .../pl-api/lib/entities/admin/announcement.ts | 2 +- packages/pl-api/lib/entities/admin/relay.ts | 2 +- packages/pl-api/lib/entities/admin/report.ts | 2 +- packages/pl-api/lib/entities/instance.ts | 2 +- packages/pl-api/lib/entities/rule.ts | 2 +- packages/pl-api/lib/entities/scrobble.ts | 2 +- packages/pl-api/lib/entities/suggestion.ts | 2 +- packages/pl-api/lib/entities/translation.ts | 2 +- packages/pl-api/lib/entities/trends-link.ts | 2 +- packages/pl-api/lib/features.ts | 1 + packages/pl-api/package.json | 2 +- packages/pl-fe/package.json | 2 +- .../components/content-type-button.tsx | 34 ++++++++++++------- .../pl-fe/src/features/preferences/index.tsx | 20 +++++++---- packages/pl-fe/src/reducers/compose.ts | 13 +++++-- packages/pl-fe/src/schemas/pl-fe/settings.ts | 2 +- packages/pl-fe/yarn.lock | 8 ++--- 19 files changed, 66 insertions(+), 38 deletions(-) diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 420047317..9651842c9 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -87,7 +87,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 9d9a59df8..026b19883 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", "postcss": "^8.4.47", "process": "^0.11.10", "punycode": "^2.1.1", 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/preferences/index.tsx b/packages/pl-fe/src/features/preferences/index.tsx index a55057a30..a97c10ba1 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 (
@@ -179,7 +187,7 @@ const Preferences = () => { )} - {features.richText && ( + {features.richText && !!defaultContentTypeOptions && ( }> { // } // }; +const updateDefaultContentType = (compose: Compose, instance: Instance) => { + const postFormats = instance.pleroma.metadata.post_formats; + + return compose.update('content_type', type => postFormats.includes(type) ? type : postFormats.includes('text/markdown') ? 'text/markdown' : postFormats[0]); +}; + const updateCompose = (state: State, key: string, updater: (compose: Compose) => Compose) => state.update(key, state.get('default')!, updater); @@ -283,7 +290,7 @@ const initialState: State = ImmutableMap({ default: ReducerCompose({ idempotencyKey: crypto.randomUUID(), resetFileKey: getResetFileKey() }), }); -const compose = (state = initialState, action: ComposeAction | EventsAction | MeAction | TimelineAction) => { +const compose = (state = initialState, action: ComposeAction | EventsAction | InstanceAction | MeAction | TimelineAction) => { switch (action.type) { case COMPOSE_TYPE_CHANGE: return updateCompose(state, action.composeId, compose => compose.withMutations(map => { @@ -589,6 +596,8 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | Me .set('quote', null)); case COMPOSE_FEDERATED_CHANGE: return updateCompose(state, action.composeId, compose => compose.update('federated', value => !value)); + case INSTANCE_FETCH_SUCCESS: + return updateCompose(state, 'default', (compose) => updateDefaultContentType(compose, action.instance)); default: return state; } diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index c0601614c..11584e66f 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -19,7 +19,7 @@ const settingsSchema = v.object({ deleteModal: v.fallback(v.boolean(), true), missingDescriptionModal: v.fallback(v.boolean(), true), defaultPrivacy: v.fallback(v.picklist(['public', 'unlisted', 'private', 'direct']), 'public'), - defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown']), 'text/plain'), + defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown', 'text/html']), 'text/plain'), themeMode: v.fallback(v.picklist(['system', 'light', 'dark', 'black']), 'system'), locale: v.fallback( v.pipe( diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index e368c3e7a..dd79bc7fb 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -7570,10 +7570,10 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pl-api@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.2.tgz#08794b017f64c58ce128074afdd2144f715359e2" - integrity sha512-HZNrEDvnL1+8yax7lZwe/vCELme8ieNQB6LzUKTQU8qqhMSk7EdLBeEUQok/csyAu0X+IaSsoWKMA91xYzpuGA== +pl-api@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.3.tgz#72d434a0ec8d713e5b227b35497da33cf26975a6" + integrity sha512-vcl3aGOy3AocQek3+S97QB0jIcF+iV66FvMqrFB/qnfo3Ryte9dcG6XcDxQ5LpogFQAILKk/Mm5uY2W9RZtwHA== dependencies: blurhash "^2.0.5" http-link-header "^1.1.3" From 0dd58b714ad18bb466954317bac72e3b37016f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 21:04:06 +0200 Subject: [PATCH 04/12] pl-fe: introduce wrench reaction button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- .../src/components/status-action-bar.tsx | 35 +++++++++++++++++-- .../pl-fe/src/features/preferences/index.tsx | 6 ++++ packages/pl-fe/src/locales/en.json | 2 ++ packages/pl-fe/src/schemas/pl-fe/settings.ts | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/pl-fe/src/components/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx index ca96e3a67..8e59f9ac1 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(); @@ -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 && ( { > + + {features.emojiReacts && ( + } > + + + )} diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index 2ec123e00..0d0362392 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1256,6 +1256,7 @@ "preferences.fields.theme": "Theme", "preferences.fields.underline_links_label": "Always underline links in posts", "preferences.fields.unfollow_modal_label": "Show confirmation dialog before unfollowing someone", + "preferences.fields.wrench_label": "Display wrench reaction button", "preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.", "preferences.notifications.advanced": "Show all notification categories", "preferences.options.content_type_html": "HTML", @@ -1512,6 +1513,7 @@ "status.visibility.local": "The post is only visible to users on your instance", "status.visibility.mutuals_only": "The post is only visible to people who mutually follow the author", "status.visibility.private": "The post is only visible to followers of the author", + "status.wrench": "Wrench reaction", "status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}", "statuses.quote_tombstone": "Post is unavailable.", "statuses.tombstone": "One or more posts are unavailable.", diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index 11584e66f..07d315d3d 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -35,6 +35,7 @@ const settingsSchema = v.object({ preserveSpoilers: v.fallback(v.boolean(), false), autoTranslate: v.fallback(v.boolean(), false), knownLanguages: v.fallback(v.array(v.string()), []), + showWrenchButton: v.fallback(v.boolean(), true), systemFont: v.fallback(v.boolean(), false), demetricator: v.fallback(v.boolean(), false), From 34d68cf2d8cb240fb2c698cd4085766af46c395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 20:35:52 +0200 Subject: [PATCH 05/12] Fix chats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-fe/src/queries/chats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pl-fe/src/queries/chats.ts b/packages/pl-fe/src/queries/chats.ts index 1ad2bb1f3..e617ab771 100644 --- a/packages/pl-fe/src/queries/chats.ts +++ b/packages/pl-fe/src/queries/chats.ts @@ -181,7 +181,7 @@ const useChatActions = (chatId: string) => { chat_id: variables.chatId, content: variables.content, id: pendingId, - created_at: new Date(), + created_at: new Date().toISOString(), account_id: account?.id, unread: true, }), From a3c599379781606aaf4d9a22ec7e16a395093791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 20:39:23 +0200 Subject: [PATCH 06/12] Default to whatever content type is supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-api/lib/entities/account.ts | 2 +- packages/pl-api/lib/entities/admin/account.ts | 2 +- .../pl-api/lib/entities/admin/announcement.ts | 2 +- packages/pl-api/lib/entities/admin/relay.ts | 2 +- packages/pl-api/lib/entities/admin/report.ts | 2 +- packages/pl-api/lib/entities/instance.ts | 2 +- packages/pl-api/lib/entities/rule.ts | 2 +- packages/pl-api/lib/entities/scrobble.ts | 2 +- packages/pl-api/lib/entities/suggestion.ts | 2 +- packages/pl-api/lib/entities/translation.ts | 2 +- packages/pl-api/lib/entities/trends-link.ts | 2 +- packages/pl-api/lib/features.ts | 1 + packages/pl-api/package.json | 2 +- packages/pl-fe/package.json | 2 +- .../components/content-type-button.tsx | 34 ++++++++++++------- .../pl-fe/src/features/preferences/index.tsx | 20 +++++++---- packages/pl-fe/src/reducers/compose.ts | 13 +++++-- packages/pl-fe/src/schemas/pl-fe/settings.ts | 2 +- packages/pl-fe/yarn.lock | 8 ++--- 19 files changed, 66 insertions(+), 38 deletions(-) diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 420047317..9651842c9 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -87,7 +87,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 9d9a59df8..026b19883 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", "postcss": "^8.4.47", "process": "^0.11.10", "punycode": "^2.1.1", 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/preferences/index.tsx b/packages/pl-fe/src/features/preferences/index.tsx index a55057a30..a97c10ba1 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 ( @@ -179,7 +187,7 @@ const Preferences = () => { )} - {features.richText && ( + {features.richText && !!defaultContentTypeOptions && ( }> { // } // }; +const updateDefaultContentType = (compose: Compose, instance: Instance) => { + const postFormats = instance.pleroma.metadata.post_formats; + + return compose.update('content_type', type => postFormats.includes(type) ? type : postFormats.includes('text/markdown') ? 'text/markdown' : postFormats[0]); +}; + const updateCompose = (state: State, key: string, updater: (compose: Compose) => Compose) => state.update(key, state.get('default')!, updater); @@ -283,7 +290,7 @@ const initialState: State = ImmutableMap({ default: ReducerCompose({ idempotencyKey: crypto.randomUUID(), resetFileKey: getResetFileKey() }), }); -const compose = (state = initialState, action: ComposeAction | EventsAction | MeAction | TimelineAction) => { +const compose = (state = initialState, action: ComposeAction | EventsAction | InstanceAction | MeAction | TimelineAction) => { switch (action.type) { case COMPOSE_TYPE_CHANGE: return updateCompose(state, action.composeId, compose => compose.withMutations(map => { @@ -589,6 +596,8 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | Me .set('quote', null)); case COMPOSE_FEDERATED_CHANGE: return updateCompose(state, action.composeId, compose => compose.update('federated', value => !value)); + case INSTANCE_FETCH_SUCCESS: + return updateCompose(state, 'default', (compose) => updateDefaultContentType(compose, action.instance)); default: return state; } diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index c0601614c..11584e66f 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -19,7 +19,7 @@ const settingsSchema = v.object({ deleteModal: v.fallback(v.boolean(), true), missingDescriptionModal: v.fallback(v.boolean(), true), defaultPrivacy: v.fallback(v.picklist(['public', 'unlisted', 'private', 'direct']), 'public'), - defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown']), 'text/plain'), + defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown', 'text/html']), 'text/plain'), themeMode: v.fallback(v.picklist(['system', 'light', 'dark', 'black']), 'system'), locale: v.fallback( v.pipe( diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index e368c3e7a..dd79bc7fb 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -7570,10 +7570,10 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pl-api@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.2.tgz#08794b017f64c58ce128074afdd2144f715359e2" - integrity sha512-HZNrEDvnL1+8yax7lZwe/vCELme8ieNQB6LzUKTQU8qqhMSk7EdLBeEUQok/csyAu0X+IaSsoWKMA91xYzpuGA== +pl-api@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.3.tgz#72d434a0ec8d713e5b227b35497da33cf26975a6" + integrity sha512-vcl3aGOy3AocQek3+S97QB0jIcF+iV66FvMqrFB/qnfo3Ryte9dcG6XcDxQ5LpogFQAILKk/Mm5uY2W9RZtwHA== dependencies: blurhash "^2.0.5" http-link-header "^1.1.3" From fa38a39dfe3ce1114d87e40303ad7728406289f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 21:04:06 +0200 Subject: [PATCH 07/12] pl-fe: introduce wrench reaction button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- .../src/components/status-action-bar.tsx | 35 +++++++++++++++++-- .../pl-fe/src/features/preferences/index.tsx | 6 ++++ packages/pl-fe/src/locales/en.json | 2 ++ packages/pl-fe/src/schemas/pl-fe/settings.ts | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/pl-fe/src/components/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx index ca96e3a67..8e59f9ac1 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(); @@ -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 && ( { > + + {features.emojiReacts && ( + } > + + + )} diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index 2ec123e00..0d0362392 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1256,6 +1256,7 @@ "preferences.fields.theme": "Theme", "preferences.fields.underline_links_label": "Always underline links in posts", "preferences.fields.unfollow_modal_label": "Show confirmation dialog before unfollowing someone", + "preferences.fields.wrench_label": "Display wrench reaction button", "preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.", "preferences.notifications.advanced": "Show all notification categories", "preferences.options.content_type_html": "HTML", @@ -1512,6 +1513,7 @@ "status.visibility.local": "The post is only visible to users on your instance", "status.visibility.mutuals_only": "The post is only visible to people who mutually follow the author", "status.visibility.private": "The post is only visible to followers of the author", + "status.wrench": "Wrench reaction", "status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}", "statuses.quote_tombstone": "Post is unavailable.", "statuses.tombstone": "One or more posts are unavailable.", diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index 11584e66f..07d315d3d 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -35,6 +35,7 @@ const settingsSchema = v.object({ preserveSpoilers: v.fallback(v.boolean(), false), autoTranslate: v.fallback(v.boolean(), false), knownLanguages: v.fallback(v.array(v.string()), []), + showWrenchButton: v.fallback(v.boolean(), true), systemFont: v.fallback(v.boolean(), false), demetricator: v.fallback(v.boolean(), false), From b7a7ea402764308cc444abe5f6ec4487748dad9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 21:14:24 +0200 Subject: [PATCH 08/12] Improve post details styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- .../status/components/detailed-status.tsx | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/pl-fe/src/features/status/components/detailed-status.tsx b/packages/pl-fe/src/features/status/components/detailed-status.tsx index 74f694291..a70dd0a51 100644 --- a/packages/pl-fe/src/features/status/components/detailed-status.tsx +++ b/packages/pl-fe/src/features/status/components/detailed-status.tsx @@ -144,35 +144,34 @@ const DetailedStatus: React.FC = ({ - - + + - - - - {actualStatus.application && ( - - - ({actualStatus.application.name}) - - )} - {actualStatus.edited_at && ( - <> - {' 路 '} -
- + {actualStatus.application && ( + <> + {' 路 '} + + ({actualStatus.application.name}) + + + )} + + {actualStatus.edited_at && ( + <> + {' 路 '} +
- -
- - )} +
+ + )} +
From c87d0a16a147af85c5f4ceeeed1db367e367fc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 21:16:11 +0200 Subject: [PATCH 09/12] pl-fe: Add title to application name link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- .../status/components/detailed-status.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/pl-fe/src/features/status/components/detailed-status.tsx b/packages/pl-fe/src/features/status/components/detailed-status.tsx index a70dd0a51..f2cb80d94 100644 --- a/packages/pl-fe/src/features/status/components/detailed-status.tsx +++ b/packages/pl-fe/src/features/status/components/detailed-status.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { FormattedDate, FormattedMessage, useIntl } from 'react-intl'; +import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import Account from 'pl-fe/components/account'; @@ -22,6 +22,10 @@ import StatusTypeIcon from './status-type-icon'; import type { SelectedStatus } from 'pl-fe/selectors'; +const messages = defineMessages({ + applicationName: { id: 'status.application_name', defaultMessage: 'Sent form {name}' }, +}); + interface IDetailedStatus { status: SelectedStatus; withMedia?: boolean; @@ -152,8 +156,14 @@ const DetailedStatus: React.FC = ({ {actualStatus.application && ( <> {' 路 '} - - ({actualStatus.application.name}) + + {actualStatus.application.name} )} From a52dad864bb4c9fcfdb86bcea3e9c18841881736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 22:47:42 +0200 Subject: [PATCH 10/12] Update en.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-fe/src/locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index 0d0362392..94ffdf38f 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1434,6 +1434,7 @@ "status.add_known_language": "Do not auto-translate posts in {language}.", "status.admin_account": "Moderate @{name}", "status.admin_status": "Open this post in the moderation interface", + "status.application_name": "Sent form {name}", "status.approval.pending": "Pending approval", "status.approval.rejected": "Rejected", "status.bookmark": "Bookmark", From 5b6599f98dac2f3b781a1ea9c41d616d78af3fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 23:23:48 +0200 Subject: [PATCH 11/12] pl-fe: WIP Move emojify to status content parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- packages/pl-api/lib/entities/account.ts | 27 ++++- .../src/components/account-hover-card.tsx | 2 +- packages/pl-fe/src/components/account.tsx | 13 ++- .../pl-fe/src/components/event-preview.tsx | 5 +- packages/pl-fe/src/components/group-card.tsx | 5 +- .../groups/popover/group-popover.tsx | 5 +- .../pl-fe/src/components/parsed-content.tsx | 19 +++- .../src/components/polls/poll-footer.tsx | 2 +- .../src/components/polls/poll-option.tsx | 14 ++- packages/pl-fe/src/components/status.tsx | 30 ++--- .../components/moved-note.tsx | 3 +- .../components/reply-group-indicator.tsx | 10 +- .../directory/components/account-card.tsx | 4 +- packages/pl-fe/src/features/emoji/emojify.tsx | 103 ++++++++++++++++++ packages/pl-fe/src/features/emoji/index.ts | 1 + .../event/components/event-header.tsx | 3 +- .../feed-suggestions/feed-suggestions.tsx | 12 +- .../group/components/group-header.tsx | 8 +- .../pl-fe/src/features/group/edit-group.tsx | 3 +- .../pl-fe/src/features/group/manage-group.tsx | 3 +- .../components/discover/group-list-item.tsx | 11 +- .../notifications/components/notification.tsx | 10 +- .../status/components/detailed-status.tsx | 3 +- .../modals/compare-history-modal.tsx | 10 +- .../modals/familiar-followers-modal.tsx | 11 +- .../steps/confirmation-step.tsx | 2 +- .../panels/pinned-accounts-panel.tsx | 5 +- .../panels/profile-fields-panel.tsx | 4 +- .../components/panels/profile-info-panel.tsx | 15 ++- .../ui/components/panels/user-panel.tsx | 6 +- .../features/ui/components/poll-preview.tsx | 2 +- .../components/profile-familiar-followers.tsx | 10 +- .../features/ui/components/profile-field.tsx | 20 +++- packages/pl-fe/src/normalizers/account.ts | 17 --- .../pl-fe/src/normalizers/announcement.ts | 4 +- packages/pl-fe/src/normalizers/group.ts | 13 --- packages/pl-fe/src/normalizers/status-edit.ts | 5 - packages/pl-fe/src/reducers/polls.ts | 7 +- packages/pl-fe/src/schemas/utils.ts | 11 +- 39 files changed, 287 insertions(+), 151 deletions(-) create mode 100644 packages/pl-fe/src/features/emoji/emojify.tsx diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 9651842c9..37c37b004 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -9,13 +9,38 @@ import { coerceObject, datetimeSchema, filteredArray } from './utils'; const filterBadges = (tags?: string[]) => tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') })); +const getDomainFromURL = (account: any): string => { + try { + const url = account.url; + return new URL(url).host; + } catch { + return ''; + } +}; + +const guessFqn = (account: any): string => { + const acct = account.acct; + const [user, domain] = acct.split('@'); + + if (domain) { + return acct; + } else { + return [user, getDomainFromURL(account)].join('@'); + } +}; + const preprocessAccount = v.transform((account: any) => { if (!account?.acct) return null; const username = account.username || account.acct.split('@')[0]; + const fqn = account.fqn || guessFqn(account); + const domain = fqn.split('@')[1] || ''; + return { username, + fqn, + 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, @@ -73,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(), ''), - note: v.fallback(v.string(), ''), + 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()), ''), 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/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.tsx b/packages/pl-fe/src/components/status.tsx index 3593aec1c..67812204d 100644 --- a/packages/pl-fe/src/components/status.tsx +++ b/packages/pl-fe/src/components/status.tsx @@ -12,6 +12,7 @@ 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 AccountContainer from 'pl-fe/containers/account-container'; +import Emojify from 'pl-fe/features/emoji/emojify'; import StatusTypeIcon from 'pl-fe/features/status/components/status-type-icon'; import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container'; import { HotKeys } from 'pl-fe/features/ui/components/hotkeys'; @@ -204,23 +205,17 @@ const Status: React.FC = (props) => { className='hover:underline' > - + + + ), group: ( - + + + ), }} @@ -234,12 +229,9 @@ const Status: React.FC = (props) => { const renderedAccounts = accounts.slice(0, 2).map(account => !!account && ( - + + + )); @@ -294,7 +286,7 @@ const Status: React.FC = (props) => { - + diff --git a/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx b/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx index c98f39a91..da8b93228 100644 --- a/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx +++ b/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx @@ -5,6 +5,7 @@ import Account from 'pl-fe/components/account'; import Icon from 'pl-fe/components/icon'; import HStack from 'pl-fe/components/ui/hstack'; import Text from 'pl-fe/components/ui/text'; +import Emojify from 'pl-fe/features/emoji/emojify'; import type { Account as AccountEntity } from 'pl-fe/normalizers/account'; @@ -27,7 +28,7 @@ const MovedNote: React.FC = ({ from, to }) => ( id='notification.move' defaultMessage='{name} moved to {targetName}' values={{ - name: , + name: , targetName: to.acct, }} /> 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 82edc5bc5..d67bae5b8 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 @@ -3,6 +3,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 { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import { makeGetStatus } from 'pl-fe/selectors'; @@ -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/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 {text}; + } + } + + 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( + {c}, + ); + } else if (unqualified in unicodeMapping) { + clearStack(); + + const { unified, shortcode } = unicodeMapping[unqualified]; + + nodes.push( + {unqualified}, + ); + } 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..dcd14a060 100644 --- a/packages/pl-fe/src/features/emoji/index.ts +++ b/packages/pl-fe/src/features/emoji/index.ts @@ -225,5 +225,6 @@ export { isCustomEmoji, isNativeEmoji, buildCustomEmojis, + validEmojiChar, emojify as default, }; 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/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) => { /> - + + + ): JSX.Element => ( +const buildLink = (account: Pick): JSX.Element => ( + > + + ); @@ -153,7 +155,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/status/components/detailed-status.tsx b/packages/pl-fe/src/features/status/components/detailed-status.tsx index f2cb80d94..6e218db4c 100644 --- a/packages/pl-fe/src/features/status/components/detailed-status.tsx +++ b/packages/pl-fe/src/features/status/components/detailed-status.tsx @@ -15,6 +15,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 QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container'; import StatusInteractionBar from './status-interaction-bar'; @@ -66,7 +67,7 @@ const DetailedStatus: React.FC = ({ - + diff --git a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx index 02c972250..aabc3559f 100644 --- a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx @@ -9,6 +9,7 @@ import Modal from 'pl-fe/components/ui/modal'; import Spinner from 'pl-fe/components/ui/spinner'; 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 { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -43,7 +44,6 @@ const CompareHistoryModal: React.FC =
{versions?.map((version) => { const content = ; - const spoilerContent = { __html: version.spoilerHtml }; const poll = typeof version.poll !== 'string' && version.poll; @@ -51,7 +51,9 @@ const CompareHistoryModal: React.FC =
{version.spoiler_text?.length > 0 && ( <> - + + +
)} @@ -71,7 +73,9 @@ const CompareHistoryModal: React.FC = role='radio' /> - + + + ))} diff --git a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx index f3725273c..4c5271839 100644 --- a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx @@ -6,6 +6,7 @@ import ScrollableList from 'pl-fe/components/scrollable-list'; import Modal from 'pl-fe/components/ui/modal'; import Spinner from 'pl-fe/components/ui/spinner'; import AccountContainer from 'pl-fe/containers/account-container'; +import Emojify from 'pl-fe/features/emoji/emojify'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import { makeGetAccount } from 'pl-fe/selectors'; @@ -31,7 +32,13 @@ const FamiliarFollowersModal: React.FC; } else { - const emptyMessage = }} />; + const emptyMessage = ( + }} + /> + ); body = ( }} + values={{ name: !!account && }} /> } onClose={onClickClose} diff --git a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx index 0acc3e4db..9d9ba62dc 100644 --- a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx @@ -64,7 +64,7 @@ const ConfirmationStep: React.FC = ({ group }) => { size='md' className='mx-auto max-w-sm [&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue' > - + diff --git a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx index 3814bcd47..0ae284fe3 100644 --- a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'; import { fetchPinnedAccounts } from 'pl-fe/actions/accounts'; import Widget from 'pl-fe/components/ui/widget'; import AccountContainer from 'pl-fe/containers/account-container'; +import Emojify from 'pl-fe/features/emoji/emojify'; import { WhoToFollowPanel } from 'pl-fe/features/ui/util/async-components'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -12,7 +13,7 @@ import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import type { Account } from 'pl-fe/normalizers/account'; interface IPinnedAccountsPanel { - account: Pick; + account: Pick; limit: number; } @@ -36,7 +37,7 @@ const PinnedAccountsPanel: React.FC = ({ account, limit }) id='pinned_accounts.title' defaultMessage='{name}鈥檚 choices' values={{ - name: , + name: , }} />} > diff --git a/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx index dc6712505..dd2be80b9 100644 --- a/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx @@ -8,7 +8,7 @@ import ProfileField from '../profile-field'; import type { Account } from 'pl-fe/normalizers/account'; interface IProfileFieldsPanel { - account: Pick; + account: Pick; } /** Custom profile fields for sidebar. */ @@ -16,7 +16,7 @@ const ProfileFieldsPanel: React.FC = ({ account }) => ( {account.fields.map((field, i) => ( - + ))} diff --git a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx index 8ffc01e9d..65805e1c5 100644 --- a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.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 { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import { usePlFeConfig } from 'pl-fe/hooks/usePlFeConfig'; import { capitalize } from 'pl-fe/utils/strings'; @@ -122,8 +123,6 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => ); } - const deactivated = account.deactivated ?? false; - const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.display_name_html }; const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' }); const badges = getBadges(); @@ -132,7 +131,11 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - + + {account.deactivated + ? + : } + {account.bot && } @@ -160,9 +163,9 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - {!!account.note_emojified && ( + {!!account.note && ( - + )} @@ -208,7 +211,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => {account.fields.length > 0 && ( {account.fields.map((field, i) => ( - + ))} )} diff --git a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx index 6d4cb889d..27a30185c 100644 --- a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx @@ -9,6 +9,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 { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import { useSettings } from 'pl-fe/hooks/useSettings'; import { getAcct } from 'pl-fe/utils/accounts'; @@ -29,7 +30,6 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) const fqn = useAppSelector((state) => displayFqn(state)); if (!account) return null; - const displayNameHtml = { __html: account.display_name_html }; const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct; const header = account.header; const verified = account.verified; @@ -67,7 +67,9 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) - + + + {verified && } diff --git a/packages/pl-fe/src/features/ui/components/poll-preview.tsx b/packages/pl-fe/src/features/ui/components/poll-preview.tsx index b6bd4cfc5..0cf89614d 100644 --- a/packages/pl-fe/src/features/ui/components/poll-preview.tsx +++ b/packages/pl-fe/src/features/ui/components/poll-preview.tsx @@ -4,7 +4,7 @@ import React from 'react'; import PollOption from 'pl-fe/components/polls/poll-option'; import Stack from 'pl-fe/components/ui/stack'; -import type { Poll } from 'pl-fe/normalizers/poll'; +import type { Poll } from 'pl-api'; interface IPollPreview { poll: Poll; diff --git a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx index 50984d8d7..fe3db1fed 100644 --- a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx +++ b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx @@ -9,6 +9,7 @@ import HoverAccountWrapper from 'pl-fe/components/hover-account-wrapper'; import HStack from 'pl-fe/components/ui/hstack'; 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 { useAppSelector } from 'pl-fe/hooks/useAppSelector'; import { useFeatures } from 'pl-fe/hooks/useFeatures'; @@ -51,12 +52,9 @@ const ProfileFamiliarFollowers: React.FC = ({ account - + + + {account.verified && } diff --git a/packages/pl-fe/src/features/ui/components/profile-field.tsx b/packages/pl-fe/src/features/ui/components/profile-field.tsx index 1e0cae751..18ea25f59 100644 --- a/packages/pl-fe/src/features/ui/components/profile-field.tsx +++ b/packages/pl-fe/src/features/ui/components/profile-field.tsx @@ -3,9 +3,12 @@ import React from 'react'; import { defineMessages, useIntl, FormatDateOptions } from 'react-intl'; import Markup from 'pl-fe/components/markup'; +import { ParsedContent } from 'pl-fe/components/parsed-content'; import HStack from 'pl-fe/components/ui/hstack'; import Icon from 'pl-fe/components/ui/icon'; +import Emojify from 'pl-fe/features/emoji/emojify'; import { CryptoAddress, LightningAddress } from 'pl-fe/features/ui/util/async-components'; +import { unescapeHTML } from 'pl-fe/utils/html'; import type { Account } from 'pl-fe/normalizers/account'; @@ -28,32 +31,35 @@ const dateFormatOptions: FormatDateOptions = { interface IProfileField { field: Account['fields'][number]; + emojis?: Account['emojis']; } /** Renders a single profile field. */ -const ProfileField: React.FC = ({ field }) => { +const ProfileField: React.FC = ({ field, emojis }) => { const intl = useIntl(); if (isTicker(field.name)) { return ( ); } else if (isZapEmoji(field.name)) { - return ; + return ; } return (
- + + +
{field.verified_at && ( @@ -62,7 +68,9 @@ const ProfileField: React.FC = ({ field }) => { )} - + + +
diff --git a/packages/pl-fe/src/normalizers/account.ts b/packages/pl-fe/src/normalizers/account.ts index 0a8d96353..9ee10a773 100644 --- a/packages/pl-fe/src/normalizers/account.ts +++ b/packages/pl-fe/src/normalizers/account.ts @@ -1,9 +1,3 @@ -import escapeTextContentForBrowser from 'escape-html'; - -import emojify from 'pl-fe/features/emoji'; -import { unescapeHTML } from 'pl-fe/utils/html'; -import { makeEmojiMap } from 'pl-fe/utils/normalizers'; - import type { Account as BaseAccount } from 'pl-api'; const getDomainFromURL = (account: Pick): string => { @@ -34,8 +28,6 @@ const normalizeAccount = (account: BaseAccount) => { const domain = fqn.split('@')[1] || ''; const note = account.note === '

' ? '' : account.note; - const emojiMap = makeEmojiMap(account.emojis); - return { mute_expires_at: null, ...account, @@ -46,15 +38,6 @@ const normalizeAccount = (account: BaseAccount) => { fqn, domain, note, - display_name_html: emojify(escapeTextContentForBrowser(account.display_name), emojiMap), - note_emojified: emojify(account.note, emojiMap), - note_plain: unescapeHTML(account.note), - fields: account.fields.map(field => ({ - ...field, - name_emojified: emojify(escapeTextContentForBrowser(field.name), emojiMap), - value_emojified: emojify(field.value, emojiMap), - value_plain: unescapeHTML(field.value), - })), }; }; diff --git a/packages/pl-fe/src/normalizers/announcement.ts b/packages/pl-fe/src/normalizers/announcement.ts index ffb1ad4c8..9574a0827 100644 --- a/packages/pl-fe/src/normalizers/announcement.ts +++ b/packages/pl-fe/src/normalizers/announcement.ts @@ -1,10 +1,10 @@ import emojify from 'pl-fe/features/emoji'; -import { makeCustomEmojiMap } from 'pl-fe/schemas/utils'; +import { makeEmojiMap } from 'pl-fe/utils/normalizers'; import type { AdminAnnouncement as BaseAdminAnnouncement, Announcement as BaseAnnouncement } from 'pl-api'; const normalizeAnnouncement = (announcement: T) => { - const emojiMap = makeCustomEmojiMap(announcement.emojis); + const emojiMap = makeEmojiMap(announcement.emojis); const contentHtml = emojify(announcement.content, emojiMap); diff --git a/packages/pl-fe/src/normalizers/group.ts b/packages/pl-fe/src/normalizers/group.ts index 9d564e0c5..856cb33b8 100644 --- a/packages/pl-fe/src/normalizers/group.ts +++ b/packages/pl-fe/src/normalizers/group.ts @@ -1,9 +1,3 @@ -import escapeTextContentForBrowser from 'escape-html'; - -import emojify from 'pl-fe/features/emoji'; -import { unescapeHTML } from 'pl-fe/utils/html'; -import { makeEmojiMap } from 'pl-fe/utils/normalizers'; - import type { Group as BaseGroup } from 'pl-api'; const getDomainFromURL = (group: Pick): string => { @@ -20,9 +14,6 @@ const normalizeGroup = (group: BaseGroup) => { const missingHeader = require('pl-fe/assets/images/header-missing.png'); const domain = getDomainFromURL(group); - const note = group.note === '

' ? '' : group.note; - - const emojiMap = makeEmojiMap(group.emojis); return { ...group, @@ -31,10 +22,6 @@ const normalizeGroup = (group: BaseGroup) => { header: group.header || group.header_static || missingHeader, header_static: group.header_static || group.header || missingHeader, domain, - note, - display_name_html: emojify(escapeTextContentForBrowser(group.display_name), emojiMap), - note_emojified: emojify(group.note, emojiMap), - note_plain: unescapeHTML(group.note), }; }; diff --git a/packages/pl-fe/src/normalizers/status-edit.ts b/packages/pl-fe/src/normalizers/status-edit.ts index 80809dc79..e5aa79149 100644 --- a/packages/pl-fe/src/normalizers/status-edit.ts +++ b/packages/pl-fe/src/normalizers/status-edit.ts @@ -6,18 +6,13 @@ import escapeTextContentForBrowser from 'escape-html'; import emojify from 'pl-fe/features/emoji'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; -import { normalizePollEdit } from './poll'; - import type { StatusEdit as BaseStatusEdit } from 'pl-api'; const normalizeStatusEdit = (statusEdit: BaseStatusEdit) => { const emojiMap = makeEmojiMap(statusEdit.emojis); - const poll = statusEdit.poll ? normalizePollEdit(statusEdit.poll, statusEdit.emojis) : null; - return { ...statusEdit, - poll, contentHtml: emojify(statusEdit.content, emojiMap), spoilerHtml: emojify(escapeTextContentForBrowser(statusEdit.spoiler_text), emojiMap), }; diff --git a/packages/pl-fe/src/reducers/polls.ts b/packages/pl-fe/src/reducers/polls.ts index 27102d118..f5015dc8d 100644 --- a/packages/pl-fe/src/reducers/polls.ts +++ b/packages/pl-fe/src/reducers/polls.ts @@ -1,16 +1,15 @@ import { Map as ImmutableMap } from 'immutable'; import { POLLS_IMPORT } from 'pl-fe/actions/importer'; -import { normalizePoll } from 'pl-fe/normalizers/poll'; -import type { Status } from 'pl-api'; +import type { Poll, Status } from 'pl-api'; import type { AnyAction } from 'redux'; -type State = ImmutableMap>; +type State = ImmutableMap; const importPolls = (state: State, polls: Array>) => state.withMutations(map => - polls.forEach(poll => map.set(poll.id, normalizePoll(poll))), + polls.forEach(poll => map.set(poll.id, poll)), ); const initialState: State = ImmutableMap(); diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts index ed75567ee..1cc70c64e 100644 --- a/packages/pl-fe/src/schemas/utils.ts +++ b/packages/pl-fe/src/schemas/utils.ts @@ -1,7 +1,5 @@ import * as v from 'valibot'; -import type { CustomEmoji } from 'pl-api'; - /** Validates individual items in an array, dropping any that aren't valid. */ const filteredArray = (schema: v.BaseSchema>) => v.pipe( @@ -14,13 +12,6 @@ const filteredArray = (schema: v.BaseSchema>) => )), ); -/** Map a list of CustomEmoji to their shortcodes. */ -const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) => - customEmojis.reduce>((result, emoji) => { - result[`:${emoji.shortcode}:`] = emoji; - return result; - }, {}); - /** valibot schema to force the value into an object, if it isn't already. */ const coerceObject = (shape: T) => v.pipe( @@ -29,4 +20,4 @@ const coerceObject = (shape: T) => v.object(shape), ); -export { filteredArray, makeCustomEmojiMap, coerceObject }; +export { filteredArray, coerceObject }; From 9b40c46d591b39286d0edef050f402ae5a47b35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Oct 2024 23:51:16 +0200 Subject: [PATCH 12/12] pl-fe: Move emojify to status content parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin miko艂ajczak --- .../src/api/hooks/admin/useAnnouncements.ts | 5 +- .../hooks/announcements/useAnnouncements.ts | 10 +- .../announcements/announcement-content.tsx | 10 +- .../components/announcements/announcement.tsx | 3 +- .../src/components/status-action-bar.tsx | 2 +- .../pl-fe/src/components/status-content.tsx | 29 ++-- .../src/components/status-language-picker.tsx | 6 +- .../pl-fe/src/components/translate-button.tsx | 4 +- .../src/features/admin/announcements.tsx | 8 +- .../chats/components/chat-message.tsx | 20 +-- .../compose/components/reply-indicator.tsx | 4 +- packages/pl-fe/src/features/emoji/index.ts | 147 ------------------ .../src/features/event/event-information.tsx | 2 +- .../components/site-banner.tsx | 12 +- .../features/ui/components/link-footer.tsx | 9 +- .../modals/compare-history-modal.tsx | 2 +- .../pl-fe/src/normalizers/announcement.ts | 20 --- packages/pl-fe/src/normalizers/poll.ts | 44 ------ packages/pl-fe/src/normalizers/status-edit.ts | 23 --- packages/pl-fe/src/normalizers/status.ts | 31 +--- packages/pl-fe/src/normalizers/translation.ts | 18 --- packages/pl-fe/src/reducers/history.ts | 7 +- packages/pl-fe/src/reducers/statuses.ts | 5 +- 23 files changed, 64 insertions(+), 357 deletions(-) delete mode 100644 packages/pl-fe/src/normalizers/announcement.ts delete mode 100644 packages/pl-fe/src/normalizers/poll.ts delete mode 100644 packages/pl-fe/src/normalizers/status-edit.ts delete mode 100644 packages/pl-fe/src/normalizers/translation.ts 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/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/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx index 8e59f9ac1..f4588cfa9 100644 --- a/packages/pl-fe/src/components/status-action-bar.tsx +++ b/packages/pl-fe/src/components/status-action-bar.tsx @@ -156,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; diff --git a/packages/pl-fe/src/components/status-content.tsx b/packages/pl-fe/src/components/status-content.tsx index 9404c59cc..ad54c7dbf 100644 --- a/packages/pl-fe/src/components/status-content.tsx +++ b/packages/pl-fe/src/components/status-content.tsx @@ -7,6 +7,7 @@ import Icon from 'pl-fe/components/icon'; import Button from 'pl-fe/components/ui/button'; 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 { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useSettings } from 'pl-fe/hooks/useSettings'; import { onlyEmoji as isOnlyEmoji } from 'pl-fe/utils/rich-content'; @@ -106,13 +107,13 @@ const StatusContent: React.FC = 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 && (