diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index 8b67698f20..4e5b35b973 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -13,14 +13,17 @@ import axios from 'axios'; import { on } from 'delegated-events'; import { throttle } from 'lodash'; +import { determineEmojiMode } from '@/mastodon/features/emoji/mode'; +import { updateHtmlWithEmoji } from '@/mastodon/features/emoji/render'; +import loadKeyboardExtensions from '@/mastodon/load_keyboard_extensions'; +import { loadLocale, getLocale } from '@/mastodon/locales'; +import { loadPolyfills } from '@/mastodon/polyfills'; +import ready from '@/mastodon/ready'; +import { assetHost } from '@/mastodon/utils/config'; +import { getNestedProperty } from '@/mastodon/utils/objects'; +import { isDarkMode } from '@/mastodon/utils/theme'; import { formatTime } from '@/mastodon/utils/time'; -import emojify from '../mastodon/features/emoji/emoji'; -import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions'; -import { loadLocale, getLocale } from '../mastodon/locales'; -import { loadPolyfills } from '../mastodon/polyfills'; -import ready from '../mastodon/ready'; - import 'cocoon-js-vanilla'; const messages = defineMessages({ @@ -38,7 +41,7 @@ const messages = defineMessages({ }, }); -function loaded() { +async function loaded() { const { messages: localeData } = getLocale(); const locale = document.documentElement.lang; @@ -75,9 +78,30 @@ function loaded() { return messageFormat.format(values) as string; }; - document.querySelectorAll('.emojify').forEach((content) => { - content.innerHTML = emojify(content.innerHTML); - }); + let emojiStyle = 'auto'; + const initialStateText = + document.getElementById('initial-state')?.textContent; + if (initialStateText) { + const stateEmojiStyle = getNestedProperty( + JSON.parse(initialStateText) as unknown, + 'meta', + 'emoji_style', + ); + if (typeof stateEmojiStyle === 'string') { + emojiStyle = stateEmojiStyle; + } + } + const emojiMode = determineEmojiMode(emojiStyle); + const darkTheme = isDarkMode(); + for (const element of document.querySelectorAll('.emojify')) { + await updateHtmlWithEmoji({ + assetHost, + element, + locale, + mode: emojiMode, + darkTheme, + }); + } document .querySelectorAll('time.formatted') diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index d855c9c3a2..3f4c761d48 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -6,7 +6,6 @@ import { throttle } from 'lodash'; import api from 'mastodon/api'; import { browserHistory } from 'mastodon/components/router'; import { countableText } from 'mastodon/features/compose/util/counter'; -import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light'; import { tagHistory } from 'mastodon/settings'; import { fetchCustomEmojiData } from '@/mastodon/features/emoji/picker'; @@ -595,7 +594,8 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, token) => { const fetchComposeSuggestionsEmojis = async (dispatch, token) => { const custom = await fetchCustomEmojiData(); - const results = emojiSearch(token.replace(':', ''), { maxResults: 5, custom }); + const { search } = await import('@/mastodon/features/emoji/emoji_mart_search_light'); + const results = search(token.replace(':', ''), { maxResults: 5, custom }); dispatch(readyComposeSuggestionsEmojis(token, results)); }; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.jsx.snap deleted file mode 100644 index 9502accb73..0000000000 --- a/app/javascript/mastodon/components/__tests__/__snapshots__/autosuggest_emoji-test.jsx.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[` > renders emoji with custom url 1`] = ` -
- foobar -
- :foobar: -
-
-`; - -exports[` > renders native emoji 1`] = ` -
- ๐Ÿ’™ -
- :foobar: -
-
-`; diff --git a/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.jsx b/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.jsx deleted file mode 100644 index 2603420aec..0000000000 --- a/app/javascript/mastodon/components/__tests__/autosuggest_emoji-test.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import renderer from 'react-test-renderer'; - -import AutosuggestEmoji from '../autosuggest_emoji'; - -describe('', () => { - it('renders native emoji', () => { - const emoji = { - native: '๐Ÿ’™', - colons: ':foobar:', - }; - const component = renderer.create(); - const tree = component.toJSON(); - - expect(tree).toMatchSnapshot(); - }); - - it('renders emoji with custom url', () => { - const emoji = { - custom: true, - imageUrl: 'http://example.com/emoji.png', - native: 'foobar', - colons: ':foobar:', - }; - const component = renderer.create(); - const tree = component.toJSON(); - - expect(tree).toMatchSnapshot(); - }); -}); diff --git a/app/javascript/mastodon/components/account_header/number_fields.tsx b/app/javascript/mastodon/components/account_header/number_fields.tsx index 50f7f67aab..3d21ac397b 100644 --- a/app/javascript/mastodon/components/account_header/number_fields.tsx +++ b/app/javascript/mastodon/components/account_header/number_fields.tsx @@ -11,6 +11,8 @@ import { FormattedDateWrapper } from '../formatted_date'; import { NumberFields, NumberFieldsItem } from '../number_fields'; import { ShortNumber } from '../short_number'; +import classes from './styles.module.scss'; + export const AccountNumberFields: FC<{ accountId: string }> = ({ accountId, }) => { @@ -33,7 +35,7 @@ export const AccountNumberFields: FC<{ accountId: string }> = ({ } return ( - + diff --git a/app/javascript/mastodon/components/account_header/styles.module.scss b/app/javascript/mastodon/components/account_header/styles.module.scss index 65775147cd..ce1d531a98 100644 --- a/app/javascript/mastodon/components/account_header/styles.module.scss +++ b/app/javascript/mastodon/components/account_header/styles.module.scss @@ -78,6 +78,8 @@ .nameWrapper { flex-grow: 1; + min-width: 0; + overflow-wrap: break-word; } .name { @@ -263,9 +265,23 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; } } +.numberFields { + @container (width >= #{$button-breakpoint}) { + --number-fields-gap: 40px; + } +} + .bio { font-size: 15px; color: var(--color-text-primary); + + :any-link { + color: var(--color-text-status-links); + + &:hover { + text-decoration: none; + } + } } .familiarFollowers { diff --git a/app/javascript/mastodon/components/account_list_item/index.tsx b/app/javascript/mastodon/components/account_list_item/index.tsx index 04817cb33e..d6da056c8c 100644 --- a/app/javascript/mastodon/components/account_list_item/index.tsx +++ b/app/javascript/mastodon/components/account_list_item/index.tsx @@ -90,7 +90,7 @@ export const AccountListItem: React.FC = ({ {handle}} > - {emoji.native - -
{emoji.colons}
- - ); - } - -} diff --git a/app/javascript/mastodon/components/autosuggest_emoji.tsx b/app/javascript/mastodon/components/autosuggest_emoji.tsx new file mode 100644 index 0000000000..42214615ef --- /dev/null +++ b/app/javascript/mastodon/components/autosuggest_emoji.tsx @@ -0,0 +1,22 @@ +import type { FC } from 'react'; + +import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis'; + +import { Emoji } from './emoji'; + +interface LegacyEmoji { + colons: string; + custom?: boolean; + native?: string; + imageUrl?: string; +} + +export const AutosuggestEmoji: FC<{ emoji: LegacyEmoji }> = ({ emoji }) => { + const emojis = useCustomEmojis(); + return ( +
+ +
{emoji.colons}
+
+ ); +}; diff --git a/app/javascript/mastodon/components/autosuggest_input.jsx b/app/javascript/mastodon/components/autosuggest_input.jsx index 9e342a353a..445ec601cf 100644 --- a/app/javascript/mastodon/components/autosuggest_input.jsx +++ b/app/javascript/mastodon/components/autosuggest_input.jsx @@ -9,7 +9,7 @@ import Overlay from 'react-overlays/Overlay'; import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; -import AutosuggestEmoji from './autosuggest_emoji'; +import { AutosuggestEmoji } from './autosuggest_emoji'; import { AutosuggestHashtag } from './autosuggest_hashtag'; const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => { diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index fae078da31..9b01d300a3 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -10,7 +10,7 @@ import Textarea from 'react-textarea-autosize'; import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; -import AutosuggestEmoji from './autosuggest_emoji'; +import { AutosuggestEmoji } from './autosuggest_emoji'; import { AutosuggestHashtag } from './autosuggest_hashtag'; const textAtCursorMatchesToken = (str, caretPosition) => { diff --git a/app/javascript/mastodon/components/emoji/index.tsx b/app/javascript/mastodon/components/emoji/index.tsx index 9b917df6ea..3989b595d7 100644 --- a/app/javascript/mastodon/components/emoji/index.tsx +++ b/app/javascript/mastodon/components/emoji/index.tsx @@ -19,6 +19,7 @@ import { stringToEmojiState, tokenizeText, } from '@/mastodon/features/emoji/render'; +import type { ExtraCustomEmojiMap } from '@/mastodon/features/emoji/types'; import { AnimateEmojiContext, CustomEmojiContext } from './context'; @@ -26,14 +27,19 @@ interface EmojiProps { code: string; showFallback?: boolean; showLoading?: boolean; + customEmoji?: ExtraCustomEmojiMap | null; } export const Emoji: FC = ({ code, showFallback = true, showLoading = true, + customEmoji: customEmojiOverride, }) => { - const customEmoji = useContext(CustomEmojiContext); + let customEmoji = useContext(CustomEmojiContext); + if (customEmojiOverride) { + customEmoji = customEmojiOverride; + } // First, set the emoji state based on the input code. const [state, setState] = useState(() => diff --git a/app/javascript/mastodon/components/list_item/index.tsx b/app/javascript/mastodon/components/list_item/index.tsx index 3e8d406752..a691762b5d 100644 --- a/app/javascript/mastodon/components/list_item/index.tsx +++ b/app/javascript/mastodon/components/list_item/index.tsx @@ -30,7 +30,7 @@ export const ListItemWrapper: React.FC = ({ return (
{icon} -
{children}
+
{children}
{sideContent && ( {sideContent} )} diff --git a/app/javascript/mastodon/components/list_item/styles.module.scss b/app/javascript/mastodon/components/list_item/styles.module.scss index 8a90234f93..26840e6738 100644 --- a/app/javascript/mastodon/components/list_item/styles.module.scss +++ b/app/javascript/mastodon/components/list_item/styles.module.scss @@ -14,6 +14,11 @@ color: var(--color-text-primary); } +.main { + min-width: 0; + overflow-wrap: break-word; +} + .title { font-weight: 500; diff --git a/app/javascript/mastodon/components/number_fields/styles.module.scss b/app/javascript/mastodon/components/number_fields/styles.module.scss index f71f690e9c..cfe6de5c52 100644 --- a/app/javascript/mastodon/components/number_fields/styles.module.scss +++ b/app/javascript/mastodon/components/number_fields/styles.module.scss @@ -1,9 +1,11 @@ .list { + --_item-gap: var(--number-fields-gap, 24px); + display: flex; flex-wrap: wrap; margin: 0; padding: 0; - gap: 4px 24px; + gap: 4px var(--_item-gap); font-size: 13px; color: var(--color-text-secondary); } diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index a68138125d..5e72d8e545 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -32,6 +32,7 @@ import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; import { StatusThreadLabel } from './status_thread_label'; import { CollectionPreviewCard } from '../features/collections/components/collection_preview_card'; +import { compareUrls } from '../utils/compare_urls'; const domParser = new DOMParser(); @@ -555,7 +556,7 @@ class Status extends ImmutablePureComponent { ).find((item) => compareUrls(item.get('url'), cardUrl)); if (taggedCollection) { - media = ; + media = ; } else { media = ( { const dispatch = useAppDispatch(); const history = useHistory(); - const { id, name, description, topic, discoverable, sensitive, items } = - useAppSelector((state) => state.collections.editor); + const { + id, + name, + description, + topic, + language, + discoverable, + sensitive, + items, + } = useAppSelector((state) => state.collections.editor); const handleNameChange = useCallback( (event: React.ChangeEvent) => { @@ -110,6 +118,7 @@ export const CollectionDetails: React.FC = () => { name, description, tag_name: topic || null, + language: language || null, discoverable, sensitive, }; @@ -125,6 +134,9 @@ export const CollectionDetails: React.FC = () => { sensitive, account_ids: items.map((item) => item.account_id), }; + if (language) { + payload.language = language; + } if (topic) { payload.tag_name = topic; } @@ -149,6 +161,7 @@ export const CollectionDetails: React.FC = () => { description, topic, discoverable, + language, sensitive, dispatch, history, @@ -392,13 +405,8 @@ const renderTagItem = (item: TagSearchResult) => ( const LanguageField: React.FC = () => { const dispatch = useAppDispatch(); - const initialLanguage = useAppSelector( - (state) => state.compose.get('default_language') as string, - ); const { language } = useAppSelector((state) => state.collections.editor); - const selectedLanguage = language ?? initialLanguage; - const handleLanguageChange = useCallback( (event: React.ChangeEvent) => { dispatch( @@ -419,7 +427,7 @@ const LanguageField: React.FC = () => { defaultMessage='Language' /> } - value={selectedLanguage} + value={language} onChange={handleLanguageChange} >