Merge commit '2b93d19d2ca18366b015e3dcde412e67625fe8f5' into glitch-soc/merge-upstream

This commit is contained in:
Claire committed 2026-04-14 18:19:27 +02:00
commit 817291c692
28 files changed
+475 -33

No files matched your search

@@ -64,15 +64,21 @@ export const ImageUploadModal: FC<
const [imageBlob, setImageBlob] = useState<Blob | null>(null);
const handleFile = useCallback((file: File) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
const result = reader.result;
if (typeof result === 'string' && result.length > 0) {
setImageSrc(result);
setStep('crop');
}
});
reader.readAsDataURL(file);
try {
parseImageFile(file, (result, isAnimated) => {
if (isAnimated) {
// If the image is animated, skip cropping and go straight to alt text.
setImageBlob(file);
setStep('alt');
} else {
setImageSrc(result);
setStep('crop');
}
});
} catch (error) {
console.warn('Error with image parsing:', error);
setStep('select');
}
}, []);
const handleCrop = useCallback(
@@ -104,19 +110,20 @@ export const ImageUploadModal: FC<
);
const handleCancel = useCallback(() => {
switch (step) {
case 'crop':
setImageSrc(null);
setStep('select');
break;
case 'alt':
setImageBlob(null);
if (step === 'crop') {
setImageSrc(null);
setStep('select');
} else if (step === 'alt') {
setImageBlob(null);
if (imageSrc) {
setStep('crop');
break;
default:
onClose();
} else {
setStep('select');
}
} else {
onClose();
}
}, [onClose, step]);
}, [imageSrc, onClose, step]);
return (
<DialogModal
@@ -401,6 +408,54 @@ const StepAlt: FC<{
);
};
/**
* Parses an image file and determines if it's an animated GIF and returns a data URI for cropping.
* Based on https://gist.github.com/zakirt/faa4a58cec5a7505b10e3686a226f285.
*/
function parseImageFile(
file: File,
cb: (buffer: string, isAnimated: boolean) => void,
): void {
const reader = new FileReader();
reader.onload = () => {
const buffer = reader.result;
if (!(buffer instanceof ArrayBuffer)) {
throw new Error('Expected an ArrayBuffer');
}
// Convert the ArrayBuffer to a base64 data URI.
const bytes = new Uint8Array(buffer);
const base64 = btoa(String.fromCharCode(...bytes));
const dataUri = `data:${file.type};base64,${base64}`;
// If the file type is not a GIF, then it's not animated as we don't support animated WebP or PNG.
if (file.type !== 'image/gif') {
cb(dataUri, false);
}
const view = new DataView(buffer, 10); // Start from the last 4 bytes of the Logical Screen Descriptor.
let offset = 3;
// Check the first bit for the global color table flag.
const globalColorTable = view.getInt8(0);
if (globalColorTable & 0x08) {
// Grab last three bits to calculate the global color table size, and skip it.
offset += 3 * Math.pow(2, (globalColorTable & 0x7) + 1);
}
// Check Graphics Control Extension and Graphics Control Label to access animated data.
let delayTime = 0;
if (view.getUint8(offset) & 0x21 && view.getUint8(offset + 1) & 0xf9) {
// Skip to the delay time, which is stored in the next two bytes.
delayTime = view.getUint16(offset + 4);
}
// If there is a delay time, the GIF is animated.
cb(dataUri, delayTime > 0);
};
reader.readAsArrayBuffer(file);
}
async function calculateCroppedImage(
imageSrc: string,
crop: Area,
@@ -427,10 +482,7 @@ async function calculateCroppedImage(
crop.height,
);
return canvas.convertToBlob({
quality: 0.7,
type: 'image/jpeg',
});
return canvas.convertToBlob();
}
function dataUriToImage(dataUri: string) {
@@ -189,7 +189,7 @@ const AccountNameHelp: FC<{
</ol>
<FormattedMessage
id='account.name.help.footer'
defaultMessage='Just like you can send emails to people using different email clients, you can interact with people on other Mastodon servers – and with anyone on other social apps powered by the same set of rules as Mastodon uses (the ActivityPub protocol).'
defaultMessage='Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Mastodon-compatible social apps.'
tagName='p'
/>
@@ -67,6 +67,10 @@
flex-wrap: nowrap;
overflow: hidden;
position: relative;
> button {
flex-shrink: 0;
}
}
.tagsListShowAll {
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Вас уключылі ў гэтую калекцыю",
"collections.edit_details": "Рэдагаваць падрабязнасці",
"collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.",
"collections.hidden_accounts_description": "Вы заблакіравалі або ігнаруеце {count, plural, one {гэтага карыстальніка} other {гэтых карыстальнікаў}}",
"collections.hidden_accounts_link": "{count, plural, one {# схаваны ўліковы запіс} few {# схаваныя ўліковыя запісы} other {# схаваных уліковых запісаў}}",
"collections.hints.accounts_counter": "{count} / {max} уліковых запісаў",
"collections.last_updated_at": "Апошняе абнаўленне: {date}",
"collections.manage_accounts": "Кіраванне ўліковымі запісамі",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Du er med i denne samling",
"collections.edit_details": "Rediger detaljer",
"collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.",
"collections.hidden_accounts_description": "Du har blokeret eller skjult {count, plural, one {denne bruger} other {disse brugere}}",
"collections.hidden_accounts_link": "{count, plural, one {# skjult konto} other {# skjulte konti}}",
"collections.hints.accounts_counter": "{count} / {max} konti",
"collections.last_updated_at": "Senest opdateret: {date}",
"collections.manage_accounts": "Administrer konti",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Du bist ein Teil dieser Sammlung",
"collections.edit_details": "Details bearbeiten",
"collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.",
"collections.hidden_accounts_description": "Du hast {count, plural, one {dieses Profil} other {diese Profile}} blockiert oder stummgeschaltet",
"collections.hidden_accounts_link": "{count, plural, one {# Konto ausgeblendet} other {# Konten ausgeblendet}}",
"collections.hints.accounts_counter": "{count}/{max} Konten",
"collections.last_updated_at": "Aktualisiert: {date}",
"collections.manage_accounts": "Profile verwalten",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Είστε αναδεδειγμένοι σε αυτήν τη συλλογή",
"collections.edit_details": "Επεξεργασία λεπτομερειών",
"collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.",
"collections.hidden_accounts_description": "Έχετε αποκλείσει ή κάνει σίγαση {count, plural, one {αυτόν τον χρήστη} other {αυτούς τους χρήστες}}",
"collections.hidden_accounts_link": "{count, plural, one {# κρυμμένος λογαριασμός} other {# κρυμμένοι λογαριασμοί}}",
"collections.hints.accounts_counter": "{count} / {max} λογαριασμοί",
"collections.last_updated_at": "Τελευταία ενημέρωση: {date}",
"collections.manage_accounts": "Διαχείριση λογαριασμών",
+1 -1
View File
@@ -106,7 +106,7 @@
"account.name.copy": "Copy handle",
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
"account.name.help.domain_self": "{domain} is your server that hosts your profile and posts.",
"account.name.help.footer": "Just like you can send emails to people using different email clients, you can interact with people on other Mastodon servers – and with anyone on other social apps powered by the same set of rules as Mastodon uses (the ActivityPub protocol).",
"account.name.help.footer": "Just like you can send emails to people using different email providers, you can interact with people on other Mastodon servers, and with anyone on other Mastodon-compatible social apps.",
"account.name.help.header": "A handle is like an email address",
"account.name.help.username": "{username} is this account’s username on their server. Someone on another server might have the same username.",
"account.name.help.username_self": "{username} is your username on this server. Someone on another server might have the same username.",
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Te destacaron en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Bloqueaste o silenciaste a {count, plural, one {este usuario} other {estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count} / {max} cuentas",
"collections.last_updated_at": "Última actualización: {date}",
"collections.manage_accounts": "Administrar cuentas",
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Apareces en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural, one {a este usuario} other {a estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count} / {max} cuentas",
"collections.last_updated_at": "Última actualización: {date}",
"collections.manage_accounts": "Administrar cuentas",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Apareces en esta colección",
"collections.edit_details": "Editar detalles",
"collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.",
"collections.hidden_accounts_description": "Has bloqueado o silenciado {count, plural,one {a este usuario}other {a estos usuarios}}",
"collections.hidden_accounts_link": "{count, plural, one {# cuenta oculta} other {# cuentas ocultas}}",
"collections.hints.accounts_counter": "{count} / {max} cuentas",
"collections.last_updated_at": "Última actualización: {date}",
"collections.manage_accounts": "Administrar cuentas",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Esiinnyt tässä kokoelmassa",
"collections.edit_details": "Muokkaa tietoja",
"collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.",
"collections.hidden_accounts_description": "Olet estänyt tai mykistänyt {count, plural, one {tämän käyttäjän} other {nämä käyttäjät}}",
"collections.hidden_accounts_link": "{count, plural, one {# piilotettu tili} other {# piilotettua tiliä}}",
"collections.hints.accounts_counter": "{count} / {max} tiliä",
"collections.last_updated_at": "Päivitetty viimeksi {date}",
"collections.manage_accounts": "Hallitse tilejä",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Þú kemur fyrir í þessu safni",
"collections.edit_details": "Breyta ítarupplýsingum",
"collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.",
"collections.hidden_accounts_description": "Þú hefur lokað á eða þaggað niður í {count, plural, one {þessum notanda} other {þessum notendum}}",
"collections.hidden_accounts_link": "{count, plural, one {# falinn aðgangur} other {# faldir aðgangar}}",
"collections.hints.accounts_counter": "{count} / {max} aðgangar",
"collections.last_updated_at": "Síðast uppfært: {date}",
"collections.manage_accounts": "Sýsla með notandaaðganga",
+2
View File
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Sei presente in questa collezione",
"collections.edit_details": "Modifica i dettagli",
"collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.",
"collections.hidden_accounts_description": "Hai bloccato o silenziato {count, plural, one {questo utente} other {questi utenti}}",
"collections.hidden_accounts_link": "{count, plural, one {# account nascosto} other {# account nascosti}}",
"collections.hints.accounts_counter": "{count} / {max} account",
"collections.last_updated_at": "Ultimo aggiornamento: {date}",
"collections.manage_accounts": "Gestisci account",
+16 -1
View File
@@ -194,27 +194,41 @@
"collections.accounts.empty_description": "フォローしている {count} アカウントまで追加できます",
"collections.accounts.empty_title": "このコレクションは空です",
"collections.by_account": "{account_handle} による",
"collections.collection_description": "詳細",
"collections.collection_language": "言語",
"collections.collection_name": "名前",
"collections.collection_topic": "トピック",
"collections.confirm_account_removal": "このコレクションからこのアカウントを削除してもよろしいですか?",
"collections.content_warning": "閲覧注意",
"collections.continue": "次に進む",
"collections.create.accounts_subtitle": "あなたがフォローしていてディスカバリー機能にオプトインしたアカウントのみを追加することができます。",
"collections.create.accounts_title": "このコレクションで誰に注目しますか?",
"collections.create.basic_details_title": "基本情報",
"collections.create.steps": "ステップ {step}/{total}",
"collections.create_a_collection_hint": "お気に入りのアカウントを他の人に勧めたり共有するコレクションを作成しましょう。",
"collections.create_collection": "コレクションを作成",
"collections.delete_collection": "コレクションを削除",
"collections.description_length_hint": "100 文字制限",
"collections.description_length_hint": "100 文字まで",
"collections.detail.accounts_heading": "アカウント",
"collections.detail.author_added_you_on_date": "{author} があなたを {date} に追加しました",
"collections.detail.loading": "コレクションを読み込み中…",
"collections.detail.share": "コレクションを共有",
"collections.detail.you_are_in_this_collection": "あなたはこのコレクションで紹介されています",
"collections.hints.accounts_counter": "{count} / {max} アカウント",
"collections.mark_as_sensitive": "閲覧注意としてマーク",
"collections.mark_as_sensitive_hint": "コレクションの説明とアカウントを閲覧注意で隠します。コレクション名は表示されます。",
"collections.name_length_hint": "40 文字まで",
"collections.new_collection": "新規のコレクション",
"collections.no_collections_yet": "コレクションはまだありません。",
"collections.remove_account": "このアカウントを削除する",
"collections.report_collection": "このコレクションを通報する",
"collections.search_accounts_label": "追加するアカウントを探す…",
"collections.topic_hint": "ハッシュタグを追加して、このコレクションの主なトピックを知ってもらいましょう。",
"collections.visibility_public": "公開",
"collections.visibility_public_hint": "検索結果やその他おすすめに表示されて、見つけてもらうことができます。",
"collections.visibility_title": "公開範囲",
"collections.visibility_unlisted": "ひかえめな公開",
"collections.visibility_unlisted_hint": "リンクを知っている人が見られます。検索結果やその他おすすめには表示さません。",
"column.about": "概要",
"column.blocks": "ブロックしたユーザー",
"column.bookmarks": "ブックマーク",
@@ -461,6 +475,7 @@
"footer.source_code": "ソースコードを表示",
"footer.status": "ステータス",
"footer.terms_of_service": "サービス利用規約",
"form_field.optional": "(省略可能)",
"generic.saved": "保存しました",
"getting_started.heading": "スタート",
"hashtag.admin_moderation": "#{name}のモデレーション画面を開く",
@@ -45,6 +45,7 @@
"account.featured": "精選ê",
"account.featured.accounts": "個人資料",
"account.featured.collections": "收藏",
"account.featured.new_collection": "新ê收藏",
"account.field_overflow": "展示規篇內容",
"account.filters.all": "逐ē活動",
"account.filters.boosts_toggle": "顯示轉PO",
@@ -102,6 +103,7 @@
"account.muted": "消音ah",
"account.muting": "消音",
"account.mutual": "Lín sio跟tuè",
"account.name.copy": "Khóo-pih口座ê名",
"account.name.help.domain": "",
"account.name.help.domain_self": "{domain} 是管理lí ê個人資料hām PO文ê服侍器。",
"account.name.help.footer": "Tiō親像lí通用別款電子phue程式寄電子phue予別lâng,lí ē當佇別ê Mastodon服侍器hām別lâng交流,koh ē當hām用kap Mastodon kâng款規則(ActivityPub 協定)ê別款社里軟體ê lâng交流。",
@@ -138,6 +140,9 @@
"account.unmute": "取消消音 @{name}",
"account.unmute_notifications_short": "Kā通知取消消音",
"account.unmute_short": "取消消音",
"account_edit.advanced_settings.bot_hint": "Kā別lâng講tsit ê口座主要行自動操作,可能無lâng監控",
"account_edit.advanced_settings.bot_label": "機器lâng ê口座",
"account_edit.advanced_settings.title": "進一步ê設定",
"account_edit.bio.add_label": "加添個人紹介",
"account_edit.bio.edit_label": "編個人紹介",
"account_edit.bio.placeholder": "加一段短紹介,幫tsān別lâng認捌lí。",
@@ -346,6 +351,7 @@
"closed_registrations_modal.find_another_server": "Tshuē別ê服侍器",
"closed_registrations_modal.preamble": "因為Mastodon非中心化,所以bô論tī tá tsi̍t ê服侍器建立口座,lí lóng ē當跟tuè tsi̍t ê服侍器ê逐ê lâng,kap hām in交流。Lí iā ē當ka-tī起tsi̍t ê站!",
"closed_registrations_modal.title": "註冊 Mastodon ê口座",
"collection.share_modal.share_link_label": "分享連結",
"collection.share_modal.share_via_post": "PO佇Mastodon頂",
"collection.share_modal.share_via_system": "分享kàu……",
"collection.share_modal.title": "分享收藏",
@@ -426,7 +432,9 @@
"column.list_members": "管理列單ê成員",
"column.lists": "列單",
"column.mutes": "消音ê用者",
"column.my_collections": "我ê收藏",
"column.notifications": "通知",
"column.other_collections": "{name} ê收藏",
"column.pins": "釘起來ê PO文",
"column.public": "聯邦ê時間線",
"column_back_button.label": "頂頁",
@@ -496,6 +504,10 @@
"confirmations.follow_to_list.confirm": "跟tuè,加入kàu列單",
"confirmations.follow_to_list.message": "Beh kā {name} 加添kàu列單,lí tio̍h先跟tuè伊。",
"confirmations.follow_to_list.title": "Kám beh跟tuè tsit ê用者?",
"confirmations.hide_featured_tab.confirm": "Khàm掉分頁",
"confirmations.hide_featured_tab.intro": "Lí不管時ē當佇<i>編輯個人資料→個人資料分頁設定</i>下kha改tse。",
"confirmations.hide_featured_tab.message": "Tse ē kā佇 {serverName} kap別ê pháng上新版本ê Mastodon ê服侍器ê用者khàm掉分頁。其他界面ê顯示可能無kâng款。",
"confirmations.hide_featured_tab.title": "敢beh khàm掉「精選ê」分頁?",
"confirmations.logout.confirm": "登出",
"confirmations.logout.message": "Lí kám確定beh登出?",
"confirmations.logout.title": "Lí kám beh登出?",
@@ -575,7 +587,9 @@
"domain_pill.your_server": "Lí數位ê厝,內底有lí所有ê PO文。無kah意?Ē當轉kàu別ê服侍器,koh保有跟tuè lí êl âng。.",
"domain_pill.your_username": "Lí 佇tsit ê服侍器獨一ê稱呼。佇無kâng ê服侍器有可能tshuē著kāng名ê用者。",
"dropdown.empty": "揀選項",
"email_subscriptions.email": "電子phue箱",
"email_subscriptions.form.action": "訂",
"email_subscriptions.form.bottom": "無開Mastodon ê口座,mā ē當佇收件箱收著PO文。不管時lóng ē當取消訂。其他資訊,請參考<a>隱私權政策</a>。",
"email_subscriptions.form.title": "訂 {name} ê電子phue更新",
"email_subscriptions.submitted.lead": "請檢查lí ê收件箱來完成訂電子批ê更新。",
"email_subscriptions.submitted.title": "上尾步",
+3 -1
View File
@@ -85,7 +85,7 @@
"account.menu.copy": "Link kopiëren",
"account.menu.direct": "Privébericht",
"account.menu.hide_reblogs": "Boosts op tijdlijn verbergen",
"account.menu.mention": "Vermelding",
"account.menu.mention": "Vermelden",
"account.menu.mute": "Account negeren",
"account.menu.note.description": "Alleen voor jou zichtbaar",
"account.menu.open_original_page": "Op {domain} bekijken",
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "Je wordt in deze verzameling uitgelicht",
"collections.edit_details": "Gegevens bewerken",
"collections.error_loading_collections": "Er is een fout opgetreden bij het laden van je verzamelingen.",
"collections.hidden_accounts_description": "Je hebt {count, plural, one {deze gebruiker} other {deze gebruikers}} geblokkeerd",
"collections.hidden_accounts_link": "{count, plural, one {# verborgen account} other {# verborgen accounts}}",
"collections.hints.accounts_counter": "{count} / {max} accounts",
"collections.last_updated_at": "Laatst bijgewerkt: {date}",
"collections.manage_accounts": "Accounts beheren",
+2
View File
@@ -383,6 +383,8 @@
"collections.detail.you_are_in_this_collection": "Shfaqeni te ky koleksion",
"collections.edit_details": "Përpunoni hollësi",
"collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.",
"collections.hidden_accounts_description": "Bllokuat, ose heshtuat {count, plural, one {këtë përdorues} other {këta përdorues}}",
"collections.hidden_accounts_link": "{count, plural, one {# llogari e fshehur} other {# llogari të fshehura}}",
"collections.hints.accounts_counter": "{count} / {max} llogari",
"collections.last_updated_at": "Përditësuar së fundi më: {date}",
"collections.manage_accounts": "Administroni llogari",
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "你被添加到了此收藏列表",
"collections.edit_details": "编辑详情",
"collections.error_loading_collections": "加载你的收藏列表时发生错误。",
"collections.hidden_accounts_description": "你已屏蔽或隐藏{count, plural, other {部分用户}}",
"collections.hidden_accounts_link": "{count, plural, other {# 个隐藏账号}}",
"collections.hints.accounts_counter": "{count} / {max} 个账号",
"collections.last_updated_at": "最后更新:{date}",
"collections.manage_accounts": "管理账户",
@@ -388,6 +388,8 @@
"collections.detail.you_are_in_this_collection": "您已被加入至此收藏名單",
"collections.edit_details": "編輯詳細資料",
"collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。",
"collections.hidden_accounts_description": "您已封鎖或靜音{count, plural, other {這些使用者}}",
"collections.hidden_accounts_link": "{count, plural, other {# 個隱藏帳號}}",
"collections.hints.accounts_counter": "{count} / {max} 個帳號",
"collections.last_updated_at": "最後更新:{date}",
"collections.manage_accounts": "管理帳號",