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/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 464ba6a0c2..2754309fbe 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -6,7 +6,6 @@ import { throttle } from 'lodash'; import api from 'flavours/glitch/api'; import { browserHistory } from 'flavours/glitch/components/router'; import { countableText } from 'flavours/glitch/features/compose/util/counter'; -import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light'; import { tagHistory } from 'flavours/glitch/settings'; import { fetchCustomEmojiData } from '@/flavours/glitch/features/emoji/picker'; import { recoverHashtags } from 'flavours/glitch/utils/hashtag'; @@ -627,7 +626,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('@/flavours/glitch/features/emoji/emoji_mart_search_light'); + const results = search(token.replace(':', ''), { maxResults: 5, custom }); dispatch(readyComposeSuggestionsEmojis(token, results)); }; diff --git a/app/javascript/flavours/glitch/components/account_header/number_fields.tsx b/app/javascript/flavours/glitch/components/account_header/number_fields.tsx index 62eb36957a..a9ccda6eae 100644 --- a/app/javascript/flavours/glitch/components/account_header/number_fields.tsx +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/account_header/styles.module.scss b/app/javascript/flavours/glitch/components/account_header/styles.module.scss index 65775147cd..ce1d531a98 100644 --- a/app/javascript/flavours/glitch/components/account_header/styles.module.scss +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/account_list_item/index.tsx b/app/javascript/flavours/glitch/components/account_list_item/index.tsx index 629a002f41..2a357f72b2 100644 --- a/app/javascript/flavours/glitch/components/account_list_item/index.tsx +++ b/app/javascript/flavours/glitch/components/account_list_item/index.tsx @@ -93,7 +93,7 @@ export const AccountListItem: React.FC = ({ {handle}} > - {emoji.native - -
{emoji.colons}
- - ); - } - -} diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.tsx b/app/javascript/flavours/glitch/components/autosuggest_emoji.tsx new file mode 100644 index 0000000000..8ab9ce1de2 --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.tsx @@ -0,0 +1,22 @@ +import type { FC } from 'react'; + +import { useCustomEmojis } from '@/flavours/glitch/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/flavours/glitch/components/autosuggest_input.jsx b/app/javascript/flavours/glitch/components/autosuggest_input.jsx index 9e342a353a..445ec601cf 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.jsx +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/autosuggest_textarea.jsx b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx index fae078da31..9b01d300a3 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/emoji/index.tsx b/app/javascript/flavours/glitch/components/emoji/index.tsx index b2e5e5fb66..3db6da7ed7 100644 --- a/app/javascript/flavours/glitch/components/emoji/index.tsx +++ b/app/javascript/flavours/glitch/components/emoji/index.tsx @@ -19,6 +19,7 @@ import { stringToEmojiState, tokenizeText, } from '@/flavours/glitch/features/emoji/render'; +import type { ExtraCustomEmojiMap } from '@/flavours/glitch/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/flavours/glitch/components/list_item/index.tsx b/app/javascript/flavours/glitch/components/list_item/index.tsx index 3e8d406752..a691762b5d 100644 --- a/app/javascript/flavours/glitch/components/list_item/index.tsx +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/list_item/styles.module.scss b/app/javascript/flavours/glitch/components/list_item/styles.module.scss index 8a90234f93..26840e6738 100644 --- a/app/javascript/flavours/glitch/components/list_item/styles.module.scss +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/number_fields/styles.module.scss b/app/javascript/flavours/glitch/components/number_fields/styles.module.scss index f71f690e9c..cfe6de5c52 100644 --- a/app/javascript/flavours/glitch/components/number_fields/styles.module.scss +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 91b5f7a2a9..b64499e0c4 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -31,6 +31,7 @@ import StatusContent from './status_content'; import StatusIcons from './status_icons'; import StatusPrepend from './status_prepend'; import { CollectionPreviewCard } from '../features/collections/components/collection_preview_card'; +import { compareUrls } from '../utils/compare_urls'; const domParser = new DOMParser(); @@ -649,7 +650,7 @@ class Status extends ImmutablePureComponent { status.get('tagged_collections') ).find((item) => compareUrls(item.get('url'), cardUrl)); if (taggedCollection) { - media.push(); + media.push(); } else { media.push( { - 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/flavours/glitch/features/collections/editor/details.tsx b/app/javascript/flavours/glitch/features/collections/editor/details.tsx index f120143ba6..954ab0842d 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/details.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/details.tsx @@ -46,8 +46,16 @@ import { WizardStepTitle } from './wizard_step_title'; export const CollectionDetails: React.FC = () => { 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} >
- ); - } - -} 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} >