Merge commit 'dbb20f76a781defe35d077529c8269d712c1fbd2' into glitch-soc/merge-upstream

Conflicts:
- `tsconfig.json`:
  glitch-soc had extra paths under `app/javascript/flavours`, but upstream
  added `app/javascript` as a whole, so updated to upstream's.
This commit is contained in:
Claire committed 2025-06-25 19:29:09 +02:00
commit c9a1e27a49
91 files changed
+1108 -311

No files matched your search

@@ -1,6 +1,6 @@
import renderer from 'react-test-renderer';
import { render, fireEvent, screen } from 'mastodon/test_helpers';
import { render, fireEvent, screen } from '@/testing/rendering';
import { Button } from '../button';
@@ -0,0 +1,120 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { accountFactoryState, relationshipsFactory } from '@/testing/factories';
import { Account } from './index';
const meta = {
title: 'Components/Account',
component: Account,
argTypes: {
id: {
type: 'string',
description: 'ID of the account to display',
},
size: {
type: 'number',
description: 'Size of the avatar in pixels',
},
hidden: {
type: 'boolean',
description: 'Whether the account is hidden or not',
},
minimal: {
type: 'boolean',
description: 'Whether to display a minimal version of the account',
},
defaultAction: {
type: 'string',
control: 'select',
options: ['block', 'mute'],
description: 'Default action to take on the account',
},
withBio: {
type: 'boolean',
description: 'Whether to display the account bio or not',
},
withMenu: {
type: 'boolean',
description: 'Whether to display the account menu or not',
},
},
args: {
id: '1',
size: 46,
hidden: false,
minimal: false,
defaultAction: 'mute',
withBio: false,
withMenu: true,
},
parameters: {
state: {
accounts: {
'1': accountFactoryState(),
},
},
},
} satisfies Meta<typeof Account>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
id: '1',
},
};
export const Hidden: Story = {
args: {
hidden: true,
},
};
export const Minimal: Story = {
args: {
minimal: true,
},
};
export const WithBio: Story = {
args: {
withBio: true,
},
};
export const NoMenu: Story = {
args: {
withMenu: false,
},
};
export const Blocked: Story = {
args: {
defaultAction: 'block',
},
parameters: {
state: {
relationships: {
'1': relationshipsFactory({
blocking: true,
}),
},
},
},
};
export const Muted: Story = {
args: {},
parameters: {
state: {
relationships: {
'1': relationshipsFactory({
muting: true,
}),
},
},
},
};
@@ -18,6 +18,7 @@ import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store';
import { isProduction } from 'mastodon/utils/environment';
import { BodyScrollLock } from 'mastodon/features/ui/components/body_scroll_lock';
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
@@ -58,6 +59,7 @@ export default class Mastodon extends PureComponent {
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
<BodyScrollLock />
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
@@ -14,7 +14,6 @@ import MediaModal from 'mastodon/features/ui/components/media_modal';
import { Video } from 'mastodon/features/video';
import { IntlProvider } from 'mastodon/locales';
import { createPollFromServerJSON } from 'mastodon/models/poll';
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
@@ -34,9 +33,6 @@ export default class MediaContainer extends PureComponent {
};
handleOpenMedia = (media, index, lang) => {
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media, index, lang });
};
@@ -45,16 +41,10 @@ export default class MediaContainer extends PureComponent {
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
const mediaList = fromJS(media);
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media: mediaList, lang, options });
};
handleCloseMedia = () => {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = '0';
this.setState({
media: null,
index: null,
@@ -1,4 +1,4 @@
import { render, fireEvent, screen } from 'mastodon/test_helpers';
import { render, fireEvent, screen } from '@/testing/rendering';
import Column from '../column';
@@ -0,0 +1,71 @@
import { useLayoutEffect, useEffect, useState } from 'react';
import { createAppSelector, useAppSelector } from 'mastodon/store';
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
const getShouldLockBodyScroll = createAppSelector(
[
(state) => state.navigation.open,
(state) => state.modal.get('stack').size > 0,
],
(isMobileMenuOpen: boolean, isModalOpen: boolean) => {
return isMobileMenuOpen || isModalOpen;
},
);
/**
* This component locks scrolling on the `body` element when
* `getShouldLockBodyScroll` returns true.
*
* The scrollbar width is taken into account and written to
* a CSS custom property `--root-scrollbar-width`
*/
export const BodyScrollLock: React.FC = () => {
const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll);
useLayoutEffect(() => {
document.body.classList.toggle('with-modals--active', shouldLockBodyScroll);
}, [shouldLockBodyScroll]);
const [scrollbarWidth, setScrollbarWidth] = useState(() =>
getScrollbarWidth(),
);
useEffect(() => {
const handleResize = () => {
setScrollbarWidth(getScrollbarWidth());
};
window.addEventListener('resize', handleResize, { passive: true });
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// Inject style element to make scrollbar width available
// as CSS custom property
useLayoutEffect(() => {
const nonce = document
.querySelector('meta[name=style-nonce]')
?.getAttribute('content');
if (nonce) {
const styleEl = document.createElement('style');
styleEl.nonce = nonce;
styleEl.innerHTML = `
:root {
--root-scrollbar-width: ${scrollbarWidth}px;
}
`;
document.head.appendChild(styleEl);
return () => {
document.head.removeChild(styleEl);
};
}
return () => '';
}, [scrollbarWidth]);
return null;
};
@@ -20,7 +20,6 @@ import {
IgnoreNotificationsModal,
AnnualReportModal,
} from 'mastodon/features/ui/util/async-components';
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
import BundleContainer from '../containers/bundle_container';
@@ -90,20 +89,6 @@ export default class ModalRoot extends PureComponent {
backgroundColor: null,
};
getSnapshotBeforeUpdate () {
return { visible: !!this.props.type };
}
componentDidUpdate (prevProps, prevState, { visible }) {
if (visible) {
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
} else {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = '0';
}
}
setBackgroundColor = color => {
this.setState({ backgroundColor: color });
};
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Porušení pravidla",
"report_notification.categories.violation_sentence": "porušení pravidel",
"report_notification.open": "Otevřít hlášení",
"search.clear": "Vymazat hledání",
"search.no_recent_searches": "Žádná nedávná vyhledávání",
"search.placeholder": "Hledat",
"search.quick_action.account_search": "Profily odpovídající {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Regelverstoß",
"report_notification.categories.violation_sentence": "Regelverletzung",
"report_notification.open": "Meldung öffnen",
"search.clear": "Suchanfrage löschen",
"search.no_recent_searches": "Keine früheren Suchanfragen",
"search.placeholder": "Suchen",
"search.quick_action.account_search": "Profile passend zu {x}",
+10
View File
@@ -1,6 +1,7 @@
{
"about.blocks": "Reguligitaj serviloj",
"about.contact": "Kontakto:",
"about.default_locale": "기본",
"about.disclaimer": "Mastodon estas libera, malfermitkoda programo kaj varmarko de la firmao Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Kialo ne disponeblas",
"about.domain_blocks.preamble": "Mastodon ĝenerale rajtigas vidi la enhavojn de uzantoj el aliaj serviloj en la fediverso, kaj komuniki kun ili. Jen la limigoj deciditaj de tiu ĉi servilo mem.",
@@ -8,6 +9,7 @@
"about.domain_blocks.silenced.title": "Limigita",
"about.domain_blocks.suspended.explanation": "Neniuj datumoj el tiu servilo estos prilaboritaj, konservitaj, aŭ interŝanĝitaj, do neeblas interagi aŭ komuniki kun uzantoj de tiu servilo.",
"about.domain_blocks.suspended.title": "Suspendita",
"about.language_label": "Lingvo",
"about.not_available": "Ĉi tiu informo ne estas disponebla ĉe ĉi tiu servilo.",
"about.powered_by": "Malcentrigita socia retejo pere de {mastodon}",
"about.rules": "Reguloj de la servilo",
@@ -328,9 +330,13 @@
"errors.unexpected_crash.copy_stacktrace": "Kopii stakspuron en tondujo",
"errors.unexpected_crash.report_issue": "Raporti problemon",
"explore.suggested_follows": "Homoj",
"explore.title": "Popularaĵoj",
"explore.trending_links": "Novaĵoj",
"explore.trending_statuses": "Afiŝoj",
"explore.trending_tags": "Kradvortoj",
"featured_carousel.next": "Antaŭen",
"featured_carousel.post": "Afiŝi",
"featured_carousel.previous": "Malantaŭen",
"filter_modal.added.context_mismatch_explanation": "Ĉi tiu filtrilkategorio ne kongruas kun la kunteksto en kiu vi akcesis ĉi tiun afiŝon. Se vi volas ke la afiŝo estas ankaŭ filtrita en ĉi tiu kunteksto, vi devus redakti la filtrilon.",
"filter_modal.added.context_mismatch_title": "Ne kongruas la kunteksto!",
"filter_modal.added.expired_explanation": "Ĉi tiu filtrilkategorio eksvalidiĝis, vu bezonos ŝanĝi la eksvaliddaton por ĝi.",
@@ -547,6 +553,7 @@
"navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Elsaluti",
"navigation_bar.moderation": "Modereco",
"navigation_bar.more": "Pli",
"navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.",
"navigation_bar.preferences": "Preferoj",
@@ -777,6 +784,7 @@
"report_notification.categories.violation": "Malobservo de la regulo",
"report_notification.categories.violation_sentence": "malobservo de la regulo",
"report_notification.open": "Malfermi la raporton",
"search.clear": "검색어 지우기",
"search.no_recent_searches": "Neniuj lastaj serĉoj",
"search.placeholder": "Serĉi",
"search.quick_action.account_search": "Profiloj kiuj kongruas kun {x}",
@@ -872,7 +880,9 @@
"subscribed_languages.save": "Konservi ŝanĝojn",
"subscribed_languages.target": "Ŝanĝu abonitajn lingvojn por {target}",
"tabs_bar.home": "Hejmo",
"tabs_bar.menu": "Menuo",
"tabs_bar.notifications": "Sciigoj",
"tabs_bar.search": "Serĉi",
"terms_of_service.effective_as_of": "Ĝi ekvalidas de {date}",
"terms_of_service.title": "Kondiĉoj de uzado",
"terms_of_service.upcoming_changes_on": "Venontaj ŝanĝoj el {date}",
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Violación de regla",
"report_notification.categories.violation_sentence": "violación de regla",
"report_notification.open": "Abrir denuncia",
"search.clear": "Limpiar búsqueda",
"search.no_recent_searches": "Sin búsquedas recientes",
"search.placeholder": "Buscar",
"search.quick_action.account_search": "Perfiles que coinciden con {x}",
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Infracción de regla",
"report_notification.categories.violation_sentence": "infracción de regla",
"report_notification.open": "Abrir denuncia",
"search.clear": "Limpiar búsqueda",
"search.no_recent_searches": "Sin búsquedas recientes",
"search.placeholder": "Buscar",
"search.quick_action.account_search": "Perfiles que coinciden con {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Infracción de regla",
"report_notification.categories.violation_sentence": "infracción de regla",
"report_notification.open": "Abrir informe",
"search.clear": "Limpiar búsqueda",
"search.no_recent_searches": "No hay búsquedas recientes",
"search.placeholder": "Buscar",
"search.quick_action.account_search": "Perfiles que coinciden con {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Brotin regla",
"report_notification.categories.violation_sentence": "brot á reglu",
"report_notification.open": "Opna melding",
"search.clear": "Nullstilla leiting",
"search.no_recent_searches": "Ongar nýggjar leitingar",
"search.placeholder": "Leita",
"search.quick_action.account_search": "Vangar, ið samsvara {x}",
+3
View File
@@ -563,6 +563,8 @@
"navigation_bar.follows_and_followers": "Seguindo e seguidoras",
"navigation_bar.import_export": "Importar e exportar",
"navigation_bar.lists": "Listaxes",
"navigation_bar.live_feed_local": "En directo (local)",
"navigation_bar.live_feed_public": "En directo (federada)",
"navigation_bar.logout": "Pechar sesión",
"navigation_bar.moderation": "Moderación",
"navigation_bar.more": "Máis",
@@ -802,6 +804,7 @@
"report_notification.categories.violation": "Faltou ás regras",
"report_notification.categories.violation_sentence": "violación das regras",
"report_notification.open": "Abrir a denuncia",
"search.clear": "Limpar a busca",
"search.no_recent_searches": "Non hai buscas recentes",
"search.placeholder": "Procurar",
"search.quick_action.account_search": "Perfís coincidentes {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "הפרת כלל",
"report_notification.categories.violation_sentence": "הפרת כלל",
"report_notification.open": "פתח דו\"ח",
"search.clear": "ניקוי חיפוש",
"search.no_recent_searches": "לא נמצאו חיפושים אחרונים",
"search.placeholder": "חיפוש",
"search.quick_action.account_search": "פרופילים המכילים {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Szabálysértés",
"report_notification.categories.violation_sentence": "szabálysértés",
"report_notification.open": "Bejelentés megnyitása",
"search.clear": "Keresés törlése",
"search.no_recent_searches": "Nincsenek keresési előzmények",
"search.placeholder": "Keresés",
"search.quick_action.account_search": "Profilok a következő keresésre: {x}",
+3
View File
@@ -563,6 +563,8 @@
"navigation_bar.follows_and_followers": "팔로우와 팔로워",
"navigation_bar.import_export": "가져오기 & 내보내기",
"navigation_bar.lists": "리스트",
"navigation_bar.live_feed_local": "라이브 피드 (로컬)",
"navigation_bar.live_feed_public": "라이브 피드 (공개)",
"navigation_bar.logout": "로그아웃",
"navigation_bar.moderation": "중재",
"navigation_bar.more": "더 보기",
@@ -802,6 +804,7 @@
"report_notification.categories.violation": "규칙 위반",
"report_notification.categories.violation_sentence": "규칙 위반",
"report_notification.open": "신고 열기",
"search.clear": "검색 초기화",
"search.no_recent_searches": "최근 검색 기록이 없습니다",
"search.placeholder": "검색",
"search.quick_action.account_search": "{x}에 맞는 프로필",
+3
View File
@@ -563,6 +563,8 @@
"navigation_bar.follows_and_followers": "Volgers en gevolgde accounts",
"navigation_bar.import_export": "Importeren en exporteren",
"navigation_bar.lists": "Lijsten",
"navigation_bar.live_feed_local": "Openbare tijdlijn (deze server)",
"navigation_bar.live_feed_public": "Openbare tijdlijn (alles)",
"navigation_bar.logout": "Uitloggen",
"navigation_bar.moderation": "Moderatie",
"navigation_bar.more": "Meer",
@@ -802,6 +804,7 @@
"report_notification.categories.violation": "Overtreden regel(s)",
"report_notification.categories.violation_sentence": "serverregel overtreden",
"report_notification.open": "Rapportage openen",
"search.clear": "Zoekopdracht wissen",
"search.no_recent_searches": "Geen recente zoekopdrachten",
"search.placeholder": "Zoeken",
"search.quick_action.account_search": "Accounts die overeenkomen met {x}",
@@ -563,6 +563,8 @@
"navigation_bar.follows_and_followers": "Seguindo e seguidores",
"navigation_bar.import_export": "Importar e exportar",
"navigation_bar.lists": "Listas",
"navigation_bar.live_feed_local": "Cronologia local",
"navigation_bar.live_feed_public": "Cronologia federada",
"navigation_bar.logout": "Sair",
"navigation_bar.moderation": "Moderação",
"navigation_bar.more": "Mais",
@@ -799,6 +801,7 @@
"report_notification.categories.violation": "Violação de regra",
"report_notification.categories.violation_sentence": "violação de regra",
"report_notification.open": "Abrir denúncia",
"search.clear": "Limpar pesquisa",
"search.no_recent_searches": "Nenhuma pesquisa recente",
"search.placeholder": "Pesquisar",
"search.quick_action.account_search": "Perfis com correspondência a {x}",
+17 -13
View File
@@ -563,6 +563,8 @@
"navigation_bar.follows_and_followers": "Подписки и подписчики",
"navigation_bar.import_export": "Импорт и экспорт",
"navigation_bar.lists": "Списки",
"navigation_bar.live_feed_local": "Живая лента (локальная)",
"navigation_bar.live_feed_public": "Живая лента (глобальная)",
"navigation_bar.logout": "Выйти",
"navigation_bar.moderation": "Модерирование",
"navigation_bar.more": "Ещё",
@@ -578,9 +580,9 @@
"navigation_panel.expand_lists": "Развернуть меню списков",
"not_signed_in_indicator.not_signed_in": "Эта страница доступна только авторизованным пользователям.",
"notification.admin.report": "{name} пожаловался (-лась) на {target}",
"notification.admin.report_account": "{name} пожаловался (-лась) на {count, plural, one {# пост} few {# поста} other {# постов}} пользователя {target} по причине «{category}»",
"notification.admin.report_account": "{name} пожаловался (-лась) на {count, plural, one {# пост} few {# поста} other {# постов}} пользователя {target}, выбрав категорию «{category}»",
"notification.admin.report_account_other": "{name} пожаловался (-лась) на {count, plural, one {# пост} few {# поста} other {# постов}} пользователя {target}",
"notification.admin.report_statuses": "{name} пожаловался (-лась) на {target} по причине «{category}»",
"notification.admin.report_statuses": "{name} пожаловался (-лась) на {target}, выбрав категорию «{category}»",
"notification.admin.report_statuses_other": "{name} пожаловался (-лась) на {target}",
"notification.admin.sign_up": "{name} зарегистрировался (-лась) на сервере",
"notification.admin.sign_up.name_and_others": "{name} и ещё {count, plural, one {# пользователь} few {# пользователя} other {# пользователей}} зарегистрировались на сервере",
@@ -602,13 +604,13 @@
"notification.mentioned_you": "{name} упомянул(а) вас",
"notification.moderation-warning.learn_more": "Узнать больше",
"notification.moderation_warning": "Модераторы вынесли вам предупреждение",
"notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.",
"notification.moderation_warning.action_delete_statuses": "Некоторые ваши посты были удалены.",
"notification.moderation_warning.action_disable": "Ваша учётная запись была отключена.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые из ваших сообщений были отмечены как деликатные.",
"notification.moderation_warning.action_none": "Ваша учётная запись получила предупреждение от модерации.",
"notification.moderation_warning.action_sensitive": "С этого момента ваши сообщения будут помечены как деликатные.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые ваши посты получили отметку деликатного содержания.",
"notification.moderation_warning.action_none": "Модераторы вынесли вам предупреждение.",
"notification.moderation_warning.action_sensitive": "С этого момента ваши посты будут иметь отметку деликатного содержания.",
"notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.",
"notification.moderation_warning.action_suspend": "Действие вашей учётной записи приостановлено.",
"notification.moderation_warning.action_suspend": "Ваша учётная запись была заблокирована.",
"notification.own_poll": "Ваш опрос завершился",
"notification.poll": "Опрос, в котором вы приняли участие, завершился",
"notification.reblog": "{name} продвинул(а) ваш пост",
@@ -792,16 +794,17 @@
"report.thanks.title_actionable": "Спасибо, что сообщили о проблеме, мы рассмотрим вашу жалобу.",
"report.unfollow": "Отписаться от @{name}",
"report.unfollow_explanation": "Вы подписаны на этого пользователя. Отпишитесь от пользователя, чтобы перестать видеть посты этого человека в домашней ленте.",
"report_notification.attached_statuses": "{count, plural, one {{count} сообщение} few {{count} сообщения} many {{count} сообщений} other {{count} сообщений}} вложено",
"report_notification.attached_statuses": "{count, plural, one {{count} пост прикреплён} few {{count} поста прикреплено} other {{count} постов прикреплено}}",
"report_notification.categories.legal": "Нарушение закона",
"report_notification.categories.legal_sentence": "запрещённый контент",
"report_notification.categories.legal_sentence": "Нарушение закона",
"report_notification.categories.other": "Другое",
"report_notification.categories.other_sentence": "другое",
"report_notification.categories.other_sentence": "Другое",
"report_notification.categories.spam": "Спам",
"report_notification.categories.spam_sentence": "спам",
"report_notification.categories.spam_sentence": "Спам",
"report_notification.categories.violation": "Нарушение правил",
"report_notification.categories.violation_sentence": "нарушение правила",
"report_notification.open": "Открыть жалобу",
"report_notification.categories.violation_sentence": "Нарушение правил",
"report_notification.open": "Перейти к жалобе",
"search.clear": "Очистить поисковый запрос",
"search.no_recent_searches": "Недавние запросы отсутствуют",
"search.placeholder": "Поиск",
"search.quick_action.account_search": "Профили, соответствующие {x}",
@@ -868,6 +871,7 @@
"status.mute_conversation": "Игнорировать обсуждение",
"status.open": "Открыть пост",
"status.pin": "Закрепить в профиле",
"status.quote_error.filtered": "Скрыто одним из ваших фильтров",
"status.quote_error.removed": "Этот пост был удалён его автором.",
"status.read_more": "Читать далее",
"status.reblog": "Продвинуть",
+1
View File
@@ -779,6 +779,7 @@
"report_notification.categories.violation": "Порушення правил",
"report_notification.categories.violation_sentence": "порушення правил",
"report_notification.open": "Відкрити скаргу",
"search.clear": "Очистити пошук",
"search.no_recent_searches": "Немає останніх пошуків",
"search.placeholder": "Пошук",
"search.quick_action.account_search": "Збіг профілів {x}",
+1
View File
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "Vi phạm nội quy",
"report_notification.categories.violation_sentence": "vi phạm nội quy",
"report_notification.open": "Mở báo cáo",
"search.clear": "Xóa tìm kiếm",
"search.no_recent_searches": "Gần đây chưa tìm gì",
"search.placeholder": "Tìm kiếm",
"search.quick_action.account_search": "Người tên {x}",
@@ -804,6 +804,7 @@
"report_notification.categories.violation": "違反規則",
"report_notification.categories.violation_sentence": "違反規則",
"report_notification.open": "開啟檢舉報告",
"search.clear": "清除搜尋紀錄",
"search.no_recent_searches": "尚無最近的搜尋紀錄",
"search.placeholder": "搜尋",
"search.quick_action.account_search": "符合的個人檔案 {x}",
+4 -16
View File
@@ -1,8 +1,9 @@
import { isMobile } from '../is_mobile';
let cachedScrollbarWidth: number | null = null;
const getActualScrollbarWidth = () => {
export const getScrollbarWidth = () => {
if (isMobile(window.innerWidth)) {
return 0;
}
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll';
@@ -16,16 +17,3 @@ const getActualScrollbarWidth = () => {
return scrollbarWidth;
};
export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth;
}
const scrollbarWidth = isMobile(window.innerWidth)
? 0
: getActualScrollbarWidth();
cachedScrollbarWidth = scrollbarWidth;
return scrollbarWidth;
};
+11
View File
@@ -1648,6 +1648,17 @@ a.sparkline {
}
}
a.timestamp {
color: $darker-text-color;
text-decoration: none;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
time {
margin-inline-start: 5px;
vertical-align: baseline;
@@ -68,6 +68,7 @@ body {
&.with-modals--active {
overflow-y: hidden;
overscroll-behavior: none;
margin-right: var(--root-scrollbar-width, 0);
}
}
@@ -2894,6 +2894,11 @@ a.account__display-name {
background: var(--background-color);
backdrop-filter: var(--background-filter);
border-top: 1px solid var(--background-border-color);
box-sizing: border-box;
.with-modals--active & {
padding-right: var(--root-scrollbar-width);
}
.layout-multiple-columns & {
display: none;
@@ -3105,7 +3110,7 @@ a.account__display-name {
.search__input {
line-height: 18px;
font-size: 16px;
padding: 15px;
padding-block: 15px;
padding-inline-end: 30px;
}
@@ -8972,7 +8977,7 @@ noscript {
.search__input {
border: 1px solid var(--background-border-color);
padding: 12px;
padding-block: 12px;
padding-inline-end: 30px;
}
+53
View File
@@ -0,0 +1,53 @@
import { http, HttpResponse } from 'msw';
import { action } from 'storybook/actions';
import { relationshipsFactory } from './factories';
export const mockHandlers = {
mute: http.post<{ id: string }>('/api/v1/accounts/:id/mute', ({ params }) => {
action('muting account')(params);
return HttpResponse.json(
relationshipsFactory({ id: params.id, muting: true }),
);
}),
unmute: http.post<{ id: string }>(
'/api/v1/accounts/:id/unmute',
({ params }) => {
action('unmuting account')(params);
return HttpResponse.json(
relationshipsFactory({ id: params.id, muting: false }),
);
},
),
block: http.post<{ id: string }>(
'/api/v1/accounts/:id/block',
({ params }) => {
action('blocking account')(params);
return HttpResponse.json(
relationshipsFactory({ id: params.id, blocking: true }),
);
},
),
unblock: http.post<{ id: string }>(
'/api/v1/accounts/:id/unblock',
({ params }) => {
action('unblocking account')(params);
return HttpResponse.json(
relationshipsFactory({
id: params.id,
blocking: false,
}),
);
},
),
};
export const unhandledRequestHandler = ({ url }: Request) => {
const { pathname } = new URL(url);
if (pathname.startsWith('/api/v1/')) {
action(`unhandled request to ${pathname}`)(url);
console.warn(
`Unhandled request to ${pathname}. Please add a handler for this request in your storybook configuration.`,
);
}
};
+70
View File
@@ -0,0 +1,70 @@
import type { ApiRelationshipJSON } from '@/mastodon/api_types/relationships';
import { createAccountFromServerJSON } from '@/mastodon/models/account';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
type FactoryOptions<T> = {
id?: string;
} & Partial<T>;
type FactoryFunction<T> = (options?: FactoryOptions<T>) => T;
export const accountFactory: FactoryFunction<ApiAccountJSON> = ({
id,
...data
} = {}) => ({
id: id ?? '1',
acct: 'testuser',
avatar: '/avatars/original/missing.png',
avatar_static: '/avatars/original/missing.png',
username: 'testuser',
display_name: 'Test User',
bot: false,
created_at: '2023-01-01T00:00:00.000Z',
discoverable: true,
emojis: [],
fields: [],
followers_count: 0,
following_count: 0,
group: false,
header: '/header.png',
header_static: '/header_static.png',
indexable: true,
last_status_at: '2023-01-01',
locked: false,
mute_expires_at: null,
note: 'This is a test user account.',
statuses_count: 0,
suspended: false,
url: '/@testuser',
uri: '/users/testuser',
noindex: false,
roles: [],
hide_collections: false,
...data,
});
export const accountFactoryState = (
options: FactoryOptions<ApiAccountJSON> = {},
) => createAccountFromServerJSON(accountFactory(options));
export const relationshipsFactory: FactoryFunction<ApiRelationshipJSON> = ({
id,
...data
} = {}) => ({
id: id ?? '1',
following: false,
followed_by: false,
blocking: false,
blocked_by: false,
languages: null,
muting_notifications: false,
note: '',
requested_by: false,
muting: false,
requested: false,
domain_blocking: false,
endorsed: false,
notifying: false,
showing_reblogs: true,
...data,
});
@@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router';
import type { RenderOptions } from '@testing-library/react';
import { render as rtlRender } from '@testing-library/react';
import { IdentityContext } from './identity_context';
import { IdentityContext } from '@/mastodon/identity_context';
beforeAll(() => {
global.requestIdleCallback = vi.fn((cb: IdleRequestCallback) => {