Compare commits

...

5 Commits

Author SHA1 Message Date
gunchleoc
d15f39034a
Merge 479fc42613 into 3b52dca405 2025-07-11 17:04:07 +00:00
Claire
3b52dca405
Fix quote attributes missing from Mastodon's context (#35354)
Some checks failed
Check i18n / check-i18n (push) Has been cancelled
Chromatic / Run Chromatic (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (ruby) (push) Has been cancelled
Check formatting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
Ruby Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Historical data migration test / test (14-alpine) (push) Has been cancelled
Historical data migration test / test (15-alpine) (push) Has been cancelled
Historical data migration test / test (16-alpine) (push) Has been cancelled
Historical data migration test / test (17-alpine) (push) Has been cancelled
Ruby Testing / build (production) (push) Has been cancelled
Ruby Testing / build (test) (push) Has been cancelled
Ruby Testing / test (.ruby-version) (push) Has been cancelled
Ruby Testing / test (3.2) (push) Has been cancelled
Ruby Testing / test (3.3) (push) Has been cancelled
Ruby Testing / ImageMagick tests (.ruby-version) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.2) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.3) (push) Has been cancelled
Ruby Testing / End to End testing (.ruby-version) (push) Has been cancelled
Ruby Testing / End to End testing (3.2) (push) Has been cancelled
Ruby Testing / End to End testing (3.3) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
2025-07-11 16:35:06 +00:00
Echo
853a0c466e
Make bio hashtags open the local page instead of the remote instance (#35349) 2025-07-11 15:18:34 +00:00
GunChleoc
479fc42613 Unify array structure for locales 2025-06-06 14:59:28 +01:00
GunChleoc
764ac83157 Unified array for SUPPORTED_LOCALES and add handling for all locale codes to ActivityPub::CaseTransform 2025-06-06 14:59:27 +01:00
9 changed files with 115 additions and 75 deletions

View File

@ -26,6 +26,12 @@ module ContextHelper
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } }, attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' }, quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
quotes: {
'quote' => 'https://w3id.org/fep/044f#quote',
'quoteUri' => 'http://fedibird.com/ns#quoteUri',
'_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote',
'quoteAuthorization' => 'https://w3id.org/fep/044f#quoteAuthorization',
},
interaction_policies: { interaction_policies: {
'gts' => 'https://gotosocial.org/ns#', 'gts' => 'https://gotosocial.org/ns#',
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' }, 'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },

View File

@ -1,7 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
module LanguagesHelper module LanguagesHelper
ISO_639_1 = { # These locales can be selected as posting languages and filtered for by users.
# When adding a locale, prefer using a ISO-639-1 (2-letter) locale over
# using a ISO-639-3 (3-letter) locale if available for the language.
SUPPORTED_LOCALES = {
aa: ['Afar', 'Afaraf'].freeze, aa: ['Afar', 'Afaraf'].freeze,
ab: ['Abkhaz', 'аҧсуа бызшәа'].freeze, ab: ['Abkhaz', 'аҧсуа бызшәа'].freeze,
ae: ['Avestan', 'avesta'].freeze, ae: ['Avestan', 'avesta'].freeze,
@ -11,6 +14,7 @@ module LanguagesHelper
an: ['Aragonese', 'aragonés'].freeze, an: ['Aragonese', 'aragonés'].freeze,
ar: ['Arabic', 'اللغة العربية'].freeze, ar: ['Arabic', 'اللغة العربية'].freeze,
as: ['Assamese', 'অসমীয়া'].freeze, as: ['Assamese', 'অসমীয়া'].freeze,
ast: ['Asturian', 'Asturianu'].freeze,
av: ['Avaric', 'авар мацӀ'].freeze, av: ['Avaric', 'авар мацӀ'].freeze,
ay: ['Aymara', 'aymar aru'].freeze, ay: ['Aymara', 'aymar aru'].freeze,
az: ['Azerbaijani', 'azərbaycan dili'].freeze, az: ['Azerbaijani', 'azərbaycan dili'].freeze,
@ -27,9 +31,13 @@ module LanguagesHelper
ca: ['Catalan', 'Català'].freeze, ca: ['Catalan', 'Català'].freeze,
ce: ['Chechen', 'нохчийн мотт'].freeze, ce: ['Chechen', 'нохчийн мотт'].freeze,
ch: ['Chamorro', 'Chamoru'].freeze, ch: ['Chamorro', 'Chamoru'].freeze,
chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
co: ['Corsican', 'corsu'].freeze, co: ['Corsican', 'corsu'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze,
cr: ['Cree', 'ᓀᐦᐃᔭᐍᐏᐣ'].freeze, cr: ['Cree', 'ᓀᐦᐃᔭᐍᐏᐣ'].freeze,
cs: ['Czech', 'čeština'].freeze, cs: ['Czech', 'čeština'].freeze,
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
cu: ['Old Church Slavonic', 'ѩзыкъ словѣньскъ'].freeze, cu: ['Old Church Slavonic', 'ѩзыкъ словѣньскъ'].freeze,
cv: ['Chuvash', 'чӑваш чӗлхи'].freeze, cv: ['Chuvash', 'чӑваш чӗлхи'].freeze,
cy: ['Welsh', 'Cymraeg'].freeze, cy: ['Welsh', 'Cymraeg'].freeze,
@ -76,8 +84,10 @@ module LanguagesHelper
it: ['Italian', 'Italiano'].freeze, it: ['Italian', 'Italiano'].freeze,
iu: ['Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'].freeze, iu: ['Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'].freeze,
ja: ['Japanese', '日本語'].freeze, ja: ['Japanese', '日本語'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
jv: ['Javanese', 'basa Jawa'].freeze, jv: ['Javanese', 'basa Jawa'].freeze,
ka: ['Georgian', 'ქართული'].freeze, ka: ['Georgian', 'ქართული'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kg: ['Kongo', 'Kikongo'].freeze, kg: ['Kongo', 'Kikongo'].freeze,
ki: ['Kikuyu', 'Gĩkũyũ'].freeze, ki: ['Kikuyu', 'Gĩkũyũ'].freeze,
kj: ['Kwanyama', 'Kuanyama'].freeze, kj: ['Kwanyama', 'Kuanyama'].freeze,
@ -94,6 +104,8 @@ module LanguagesHelper
ky: ['Kyrgyz', 'Кыргызча'].freeze, ky: ['Kyrgyz', 'Кыргызча'].freeze,
la: ['Latin', 'latine'].freeze, la: ['Latin', 'latine'].freeze,
lb: ['Luxembourgish', 'Lëtzebuergesch'].freeze, lb: ['Luxembourgish', 'Lëtzebuergesch'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
lg: ['Ganda', 'Luganda'].freeze, lg: ['Ganda', 'Luganda'].freeze,
li: ['Limburgish', 'Limburgs'].freeze, li: ['Limburgish', 'Limburgs'].freeze,
ln: ['Lingala', 'Lingála'].freeze, ln: ['Lingala', 'Lingála'].freeze,
@ -107,6 +119,7 @@ module LanguagesHelper
mk: ['Macedonian', 'македонски јазик'].freeze, mk: ['Macedonian', 'македонски јазик'].freeze,
ml: ['Malayalam', 'മലയാളം'].freeze, ml: ['Malayalam', 'മലയാളം'].freeze,
mn: ['Mongolian', 'Монгол хэл'].freeze, mn: ['Mongolian', 'Монгол хэл'].freeze,
moh: ['Mohawk', 'Kanienʼkéha'].freeze,
mr: ['Marathi', 'मराठी'].freeze, mr: ['Marathi', 'मराठी'].freeze,
ms: ['Malay', 'Bahasa Melayu'].freeze, ms: ['Malay', 'Bahasa Melayu'].freeze,
'ms-Arab': ['Jawi Malay', 'بهاس ملايو'].freeze, 'ms-Arab': ['Jawi Malay', 'بهاس ملايو'].freeze,
@ -115,6 +128,7 @@ module LanguagesHelper
na: ['Nauru', 'Ekakairũ Naoero'].freeze, na: ['Nauru', 'Ekakairũ Naoero'].freeze,
nb: ['Norwegian Bokmål', 'Norsk bokmål'].freeze, nb: ['Norwegian Bokmål', 'Norsk bokmål'].freeze,
nd: ['Northern Ndebele', 'isiNdebele'].freeze, nd: ['Northern Ndebele', 'isiNdebele'].freeze,
nds: ['Low German', 'Plattdüütsch'].freeze,
ne: ['Nepali', 'नेपाली'].freeze, ne: ['Nepali', 'नेपाली'].freeze,
ng: ['Ndonga', 'Owambo'].freeze, ng: ['Ndonga', 'Owambo'].freeze,
nl: ['Dutch', 'Nederlands'].freeze, nl: ['Dutch', 'Nederlands'].freeze,
@ -129,6 +143,7 @@ module LanguagesHelper
or: ['Oriya', 'ଓଡ଼ିଆ'].freeze, or: ['Oriya', 'ଓଡ଼ିଆ'].freeze,
os: ['Ossetian', 'ирон æвзаг'].freeze, os: ['Ossetian', 'ирон æвзаг'].freeze,
pa: ['Punjabi', 'ਪੰਜਾਬੀ'].freeze, pa: ['Punjabi', 'ਪੰਜਾਬੀ'].freeze,
pdc: ['Pennsylvania Dutch', 'Pennsilfaani-Deitsch'].freeze,
pi: ['Pāli', 'पाऴि'].freeze, pi: ['Pāli', 'पाऴि'].freeze,
pl: ['Polish', 'Polski'].freeze, pl: ['Polish', 'Polski'].freeze,
ps: ['Pashto', 'پښتو'].freeze, ps: ['Pashto', 'پښتو'].freeze,
@ -141,12 +156,15 @@ module LanguagesHelper
rw: ['Kinyarwanda', 'Ikinyarwanda'].freeze, rw: ['Kinyarwanda', 'Ikinyarwanda'].freeze,
sa: ['Sanskrit', 'संस्कृतम्'].freeze, sa: ['Sanskrit', 'संस्कृतम्'].freeze,
sc: ['Sardinian', 'sardu'].freeze, sc: ['Sardinian', 'sardu'].freeze,
sco: ['Scots', 'Scots'].freeze,
sd: ['Sindhi', 'सिन्धी'].freeze, sd: ['Sindhi', 'सिन्धी'].freeze,
se: ['Northern Sami', 'Davvisámegiella'].freeze, se: ['Northern Sami', 'Davvisámegiella'].freeze,
sg: ['Sango', 'yângâ tî sängö'].freeze, sg: ['Sango', 'yângâ tî sängö'].freeze,
si: ['Sinhala', 'සිංහල'].freeze, si: ['Sinhala', 'සිංහල'].freeze,
sk: ['Slovak', 'slovenčina'].freeze, sk: ['Slovak', 'slovenčina'].freeze,
sl: ['Slovenian', 'slovenščina'].freeze, sl: ['Slovenian', 'slovenščina'].freeze,
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
sn: ['Shona', 'chiShona'].freeze, sn: ['Shona', 'chiShona'].freeze,
so: ['Somali', 'Soomaaliga'].freeze, so: ['Somali', 'Soomaaliga'].freeze,
sq: ['Albanian', 'Shqip'].freeze, sq: ['Albanian', 'Shqip'].freeze,
@ -156,6 +174,7 @@ module LanguagesHelper
su: ['Sundanese', 'Basa Sunda'].freeze, su: ['Sundanese', 'Basa Sunda'].freeze,
sv: ['Swedish', 'Svenska'].freeze, sv: ['Swedish', 'Svenska'].freeze,
sw: ['Swahili', 'Kiswahili'].freeze, sw: ['Swahili', 'Kiswahili'].freeze,
szl: ['Silesian', 'ślůnsko godka'].freeze,
ta: ['Tamil', 'தமிழ்'].freeze, ta: ['Tamil', 'தமிழ்'].freeze,
te: ['Telugu', 'తెలుగు'].freeze, te: ['Telugu', 'తెలుగు'].freeze,
tg: ['Tajik', 'тоҷикӣ'].freeze, tg: ['Tajik', 'тоҷикӣ'].freeze,
@ -165,6 +184,7 @@ module LanguagesHelper
tl: ['Tagalog', 'Tagalog'].freeze, tl: ['Tagalog', 'Tagalog'].freeze,
tn: ['Tswana', 'Setswana'].freeze, tn: ['Tswana', 'Setswana'].freeze,
to: ['Tonga', 'faka Tonga'].freeze, to: ['Tonga', 'faka Tonga'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze,
tr: ['Turkish', 'Türkçe'].freeze, tr: ['Turkish', 'Türkçe'].freeze,
ts: ['Tsonga', 'Xitsonga'].freeze, ts: ['Tsonga', 'Xitsonga'].freeze,
tt: ['Tatar', 'татар теле'].freeze, tt: ['Tatar', 'татар теле'].freeze,
@ -174,75 +194,48 @@ module LanguagesHelper
uk: ['Ukrainian', 'Українська'].freeze, uk: ['Ukrainian', 'Українська'].freeze,
ur: ['Urdu', 'اردو'].freeze, ur: ['Urdu', 'اردو'].freeze,
uz: ['Uzbek', 'Ўзбек'].freeze, uz: ['Uzbek', 'Ўзбек'].freeze,
vai: ['Vai', 'ꕙꔤ'].freeze,
ve: ['Venda', 'Tshivenḓa'].freeze, ve: ['Venda', 'Tshivenḓa'].freeze,
vi: ['Vietnamese', 'Tiếng Việt'].freeze, vi: ['Vietnamese', 'Tiếng Việt'].freeze,
vo: ['Volapük', 'Volapük'].freeze, vo: ['Volapük', 'Volapük'].freeze,
wa: ['Walloon', 'walon'].freeze, wa: ['Walloon', 'walon'].freeze,
wo: ['Wolof', 'Wollof'].freeze, wo: ['Wolof', 'Wollof'].freeze,
xal: ['Kalmyk', 'Хальмг келн'].freeze,
xh: ['Xhosa', 'isiXhosa'].freeze, xh: ['Xhosa', 'isiXhosa'].freeze,
yi: ['Yiddish', 'ייִדיש'].freeze, yi: ['Yiddish', 'ייִדיש'].freeze,
yo: ['Yoruba', 'Yorùbá'].freeze, yo: ['Yoruba', 'Yorùbá'].freeze,
za: ['Zhuang', 'Saɯ cueŋƅ'].freeze, za: ['Zhuang', 'Saɯ cueŋƅ'].freeze,
zh: ['Chinese', '中文'].freeze,
zu: ['Zulu', 'isiZulu'].freeze,
}.freeze
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze,
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
gsw: ['Swiss German', 'Schwiizertütsch'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
moh: ['Mohawk', 'Kanienʼkéha'].freeze,
nds: ['Low German', 'Plattdüütsch'].freeze,
pdc: ['Pennsylvania Dutch', 'Pennsilfaani-Deitsch'].freeze,
sco: ['Scots', 'Scots'].freeze,
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
szl: ['Silesian', 'ślůnsko godka'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze,
vai: ['Vai', 'ꕙꔤ'].freeze,
xal: ['Kalmyk', 'Хальмг келн'].freeze,
zba: ['Balaibalan', 'باليبلن'].freeze, zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze, zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
}.freeze zh: ['Chinese', '中文'].freeze,
# "Chinese" is not a language, but a language family in spite of
# e.g. For Chinese, which is not a language, # sharing the main locale code.
# but a language family in spite of sharing the main locale code
# We need to be able to filter these
ISO_639_1_REGIONAL = {
'zh-CN': ['Chinese (China)', '简体中文'].freeze, 'zh-CN': ['Chinese (China)', '简体中文'].freeze,
'zh-HK': ['Chinese (Hong Kong)', '繁體中文(香港)'].freeze, 'zh-HK': ['Chinese (Hong Kong)', '繁體中文(香港)'].freeze,
'zh-TW': ['Chinese (Taiwan)', '繁體中文(臺灣)'].freeze, 'zh-TW': ['Chinese (Taiwan)', '繁體中文(臺灣)'].freeze,
'zh-YUE': ['Cantonese', '廣東話'].freeze, 'zh-YUE': ['Cantonese', '廣東話'].freeze,
zu: ['Zulu', 'isiZulu'].freeze,
}.freeze }.freeze
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_1_REGIONAL).merge(ISO_639_3).freeze # These locales are being translated on Crowdin and are available in the UI,
# but for presenting posting and filtering languages, we drop the country code and
# For ISO-639-1 and ISO-639-3 language codes, we have their official # fall back to the language code, thus skipping these locales there.
# names, but for some translations, we need the names of the UI_ONLY_REGIONAL_LOCALES = {
# regional variants specifically 'en-GB': ['English (British)', 'English (British)'].freeze,
REGIONAL_LOCALE_NAMES = { 'es-AR': ['Spanish (Argentina)', 'Español (Argentina)'].freeze,
'en-GB': 'English (British)', 'es-MX': ['Spanish (Mexico)', 'Español (México)'].freeze,
'es-AR': 'Español (Argentina)', 'fr-CA': ['French (Canadian)', 'Français (Canadien)'].freeze,
'es-MX': 'Español (México)', 'pt-BR': ['Portuguese (Brasil)', 'Português (Brasil)'].freeze,
'fr-CA': 'Français (Canadien)', 'pt-PT': ['Portuguese (Portugal)', 'Português (Portugal)'].freeze,
'pt-BR': 'Português (Brasil)', 'sr-Latn': ['Serbian (Latin)', 'Srpski (latinica)'].freeze,
'pt-PT': 'Português (Portugal)',
'sr-Latn': 'Srpski (latinica)',
}.freeze }.freeze
KNOWN_LOCALES = {}.merge(SUPPORTED_LOCALES).merge(UI_ONLY_REGIONAL_LOCALES).freeze
# Helper for self.sorted_locale_keys # Helper for self.sorted_locale_keys
private_class_method def self.locale_name_for_sorting(locale) private_class_method def self.locale_name_for_sorting(locale)
if (supported_locale = SUPPORTED_LOCALES[locale.to_sym]) if (known_locale = KNOWN_LOCALES[locale.to_sym])
ASCIIFolding.new.fold(supported_locale[1]).downcase ASCIIFolding.new.fold(known_locale[1]).downcase
elsif (regional_locale = REGIONAL_LOCALE_NAMES[locale.to_sym])
ASCIIFolding.new.fold(regional_locale).downcase
else else
locale locale
end end
@ -256,10 +249,8 @@ module LanguagesHelper
def native_locale_name(locale) def native_locale_name(locale)
if locale.blank? || locale == 'und' if locale.blank? || locale == 'und'
I18n.t('generic.none') I18n.t('generic.none')
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym]) elsif (known_locale = KNOWN_LOCALES[locale.to_sym])
supported_locale[1] known_locale[1]
elsif (regional_locale = REGIONAL_LOCALE_NAMES[locale.to_sym])
regional_locale
else else
locale locale
end end
@ -268,8 +259,8 @@ module LanguagesHelper
def standard_locale_name(locale) def standard_locale_name(locale)
if locale.blank? if locale.blank?
I18n.t('generic.none') I18n.t('generic.none')
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym]) elsif (known_locale = KNOWN_LOCALES[locale.to_sym])
supported_locale[0] known_locale[0]
else else
locale locale
end end

View File

@ -1,12 +1,30 @@
import { useCallback } from 'react';
import { useLinks } from 'mastodon/hooks/useLinks'; import { useLinks } from 'mastodon/hooks/useLinks';
export const AccountBio: React.FC<{ interface AccountBioProps {
note: string; note: string;
className: string; className: string;
}> = ({ note, className }) => { dropdownAccountId?: string;
const handleClick = useLinks(); }
if (note.length === 0 || note === '<p></p>') { export const AccountBio: React.FC<AccountBioProps> = ({
note,
className,
dropdownAccountId,
}) => {
const handleClick = useLinks(!!dropdownAccountId);
const handleNodeChange = useCallback(
(node: HTMLDivElement | null) => {
if (!dropdownAccountId || !node || node.childNodes.length === 0) {
return;
}
addDropdownToHashtags(node, dropdownAccountId);
},
[dropdownAccountId],
);
if (note.length === 0) {
return null; return null;
} }
@ -15,6 +33,28 @@ export const AccountBio: React.FC<{
className={`${className} translate`} className={`${className} translate`}
dangerouslySetInnerHTML={{ __html: note }} dangerouslySetInnerHTML={{ __html: note }}
onClickCapture={handleClick} onClickCapture={handleClick}
ref={handleNodeChange}
/> />
); );
}; };
function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
if (!node) {
return;
}
for (const childNode of node.childNodes) {
if (!(childNode instanceof HTMLElement)) {
continue;
}
if (
childNode instanceof HTMLAnchorElement &&
(childNode.classList.contains('hashtag') ||
childNode.innerText.startsWith('#')) &&
!childNode.dataset.menuHashtag
) {
childNode.dataset.menuHashtag = accountId;
} else if (childNode.childNodes.length > 0) {
addDropdownToHashtags(childNode, accountId);
}
}
}

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { AccountBio } from '@/mastodon/components/account_bio';
import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import LockIcon from '@/material-icons/400-24px/lock.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
@ -773,7 +774,6 @@ export const AccountHeader: React.FC<{
); );
} }
const content = { __html: account.note_emojified };
const displayNameHtml = { __html: account.display_name_html }; const displayNameHtml = { __html: account.display_name_html };
const fields = account.fields; const fields = account.fields;
const isLocal = !account.acct.includes('@'); const isLocal = !account.acct.includes('@');
@ -897,12 +897,11 @@ export const AccountHeader: React.FC<{
<AccountNote accountId={accountId} /> <AccountNote accountId={accountId} />
)} )}
{account.note.length > 0 && account.note !== '<p></p>' && ( <AccountBio
<div note={account.note_emojified}
className='account__header__content translate' dropdownAccountId={accountId}
dangerouslySetInnerHTML={content} className='account__header__content'
/> />
)}
<div className='account__header__fields'> <div className='account__header__fields'>
<dl> <dl>

View File

@ -8,13 +8,14 @@ import { openURL } from 'mastodon/actions/search';
import { useAppDispatch } from 'mastodon/store'; import { useAppDispatch } from 'mastodon/store';
const isMentionClick = (element: HTMLAnchorElement) => const isMentionClick = (element: HTMLAnchorElement) =>
element.classList.contains('mention'); element.classList.contains('mention') &&
!element.classList.contains('hashtag');
const isHashtagClick = (element: HTMLAnchorElement) => const isHashtagClick = (element: HTMLAnchorElement) =>
element.textContent?.[0] === '#' || element.textContent?.[0] === '#' ||
element.previousSibling?.textContent?.endsWith('#'); element.previousSibling?.textContent?.endsWith('#');
export const useLinks = () => { export const useLinks = (skipHashtags?: boolean) => {
const history = useHistory(); const history = useHistory();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -61,12 +62,12 @@ export const useLinks = () => {
if (isMentionClick(target)) { if (isMentionClick(target)) {
e.preventDefault(); e.preventDefault();
void handleMentionClick(target); void handleMentionClick(target);
} else if (isHashtagClick(target)) { } else if (isHashtagClick(target) && !skipHashtags) {
e.preventDefault(); e.preventDefault();
handleHashtagClick(target); handleHashtagClick(target);
} }
}, },
[handleMentionClick, handleHashtagClick], [skipHashtags, handleMentionClick, handleHashtagClick],
); );
return handleClick; return handleClick;

View File

@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
? accountJSON.username ? accountJSON.username
: accountJSON.display_name; : accountJSON.display_name;
const accountNote =
accountJSON.note && accountJSON.note !== '<p></p>' ? accountJSON.note : '';
return AccountFactory({ return AccountFactory({
...accountJSON, ...accountJSON,
moved: moved?.id, moved: moved?.id,
@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
escapeTextContentForBrowser(displayName), escapeTextContentForBrowser(displayName),
emojiMap, emojiMap,
), ),
note_emojified: emojify(accountJSON.note, emojiMap), note_emojified: emojify(accountNote, emojiMap),
note_plain: unescapeHTML(accountJSON.note), note_plain: unescapeHTML(accountNote),
url: url:
accountJSON.url.startsWith('http://') || accountJSON.url.startsWith('http://') ||
accountJSON.url.startsWith('https://') accountJSON.url.startsWith('https://')

View File

@ -14,7 +14,7 @@ module ActivityPub::CaseTransform
when String when String
camel_lower_cache[value] ||= if value.start_with?('_:') camel_lower_cache[value] ||= if value.start_with?('_:')
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}" "_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) elsif LanguagesHelper::KNOWN_LOCALES.key?(value.to_sym)
value value
else else
value.underscore.camelize(:lower) value.underscore.camelize(:lower)

View File

@ -104,7 +104,7 @@ namespace :repo do
end.uniq.compact end.uniq.compact
missing_available_locales = locales_in_files - I18n.available_locales missing_available_locales = locales_in_files - I18n.available_locales
supported_locale_codes = Set.new(LanguagesHelper::SUPPORTED_LOCALES.keys + LanguagesHelper::REGIONAL_LOCALE_NAMES.keys) supported_locale_codes = Set.new(LanguagesHelper::KNOWN_LOCALES.keys)
missing_locale_names = I18n.available_locales.reject { |locale| supported_locale_codes.include?(locale) } missing_locale_names = I18n.available_locales.reject { |locale| supported_locale_codes.include?(locale) }
critical = false critical = false

View File

@ -5,7 +5,7 @@ require 'rails_helper'
RSpec.describe LanguagesHelper do RSpec.describe LanguagesHelper do
describe 'the SUPPORTED_LOCALES constant' do describe 'the SUPPORTED_LOCALES constant' do
it 'includes all i18n locales' do it 'includes all i18n locales' do
expect(Set.new(described_class::SUPPORTED_LOCALES.keys + described_class::REGIONAL_LOCALE_NAMES.keys)).to include(*I18n.available_locales) expect(Set.new(described_class::KNOWN_LOCALES.keys)).to include(*I18n.available_locales)
end end
end end