From 346a27b6fc6c113231d8ebc909af4a4c2f7913b2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 2 Dec 2024 21:07:48 +0100 Subject: [PATCH 001/195] Refactor `` to TypeScript (#33081) --- app/javascript/mastodon/components/column.jsx | 72 ------------------- app/javascript/mastodon/components/column.tsx | 52 ++++++++++++++ .../mastodon/features/directory/index.tsx | 5 +- .../mastodon/features/link_timeline/index.tsx | 5 +- .../mastodon/features/lists/index.tsx | 2 +- .../mastodon/features/lists/members.tsx | 2 +- .../mastodon/features/lists/new.tsx | 2 +- .../features/notifications_v2/index.tsx | 5 +- .../mastodon/features/onboarding/follows.tsx | 2 +- .../mastodon/features/onboarding/profile.tsx | 2 +- .../features/ui/components/column_loading.tsx | 2 +- 11 files changed, 67 insertions(+), 84 deletions(-) delete mode 100644 app/javascript/mastodon/components/column.jsx create mode 100644 app/javascript/mastodon/components/column.tsx diff --git a/app/javascript/mastodon/components/column.jsx b/app/javascript/mastodon/components/column.jsx deleted file mode 100644 index abc87a57e5..0000000000 --- a/app/javascript/mastodon/components/column.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { supportsPassiveEvents } from 'detect-passive-events'; - -import { scrollTop } from '../scroll'; - -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - -export default class Column extends PureComponent { - - static propTypes = { - children: PropTypes.node, - label: PropTypes.string, - bindToDocument: PropTypes.bool, - }; - - scrollTop () { - let scrollable = null; - - if (this.props.bindToDocument) { - scrollable = document.scrollingElement; - } else { - scrollable = this.node.querySelector('.scrollable'); - } - - if (!scrollable) { - return; - } - - this._interruptScrollAnimation = scrollTop(scrollable); - } - - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - - setRef = c => { - this.node = c; - }; - - componentDidMount () { - if (this.props.bindToDocument) { - document.addEventListener('wheel', this.handleWheel, listenerOptions); - } else { - this.node.addEventListener('wheel', this.handleWheel, listenerOptions); - } - } - - componentWillUnmount () { - if (this.props.bindToDocument) { - document.removeEventListener('wheel', this.handleWheel, listenerOptions); - } else { - this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); - } - } - - render () { - const { label, children } = this.props; - - return ( -
- {children} -
- ); - } - -} diff --git a/app/javascript/mastodon/components/column.tsx b/app/javascript/mastodon/components/column.tsx new file mode 100644 index 0000000000..01c75d85c0 --- /dev/null +++ b/app/javascript/mastodon/components/column.tsx @@ -0,0 +1,52 @@ +import { forwardRef, useRef, useImperativeHandle } from 'react'; +import type { Ref } from 'react'; + +import { scrollTop } from 'mastodon/scroll'; + +export interface ColumnRef { + scrollTop: () => void; + node: HTMLDivElement | null; +} + +interface ColumnProps { + children?: React.ReactNode; + label?: string; + bindToDocument?: boolean; +} + +export const Column = forwardRef( + ({ children, label, bindToDocument }, ref: Ref) => { + const nodeRef = useRef(null); + + useImperativeHandle(ref, () => ({ + node: nodeRef.current, + + scrollTop() { + let scrollable = null; + + if (bindToDocument) { + scrollable = document.scrollingElement; + } else { + scrollable = nodeRef.current?.querySelector('.scrollable'); + } + + if (!scrollable) { + return; + } + + scrollTop(scrollable); + }, + })); + + return ( +
+ {children} +
+ ); + }, +); + +Column.displayName = 'Column'; + +// eslint-disable-next-line import/no-default-export +export default Column; diff --git a/app/javascript/mastodon/features/directory/index.tsx b/app/javascript/mastodon/features/directory/index.tsx index d0e57600bb..ef2649a27f 100644 --- a/app/javascript/mastodon/features/directory/index.tsx +++ b/app/javascript/mastodon/features/directory/index.tsx @@ -15,7 +15,8 @@ import { changeColumnParams, } from 'mastodon/actions/columns'; import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadMore } from 'mastodon/components/load_more'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; @@ -49,7 +50,7 @@ export const Directory: React.FC<{ const intl = useIntl(); const dispatch = useAppDispatch(); - const column = useRef(null); + const column = useRef(null); const [orderParam, setOrderParam] = useSearchParam('order'); const [localParam, setLocalParam] = useSearchParam('local'); diff --git a/app/javascript/mastodon/features/link_timeline/index.tsx b/app/javascript/mastodon/features/link_timeline/index.tsx index 262a21afcc..1b3f287177 100644 --- a/app/javascript/mastodon/features/link_timeline/index.tsx +++ b/app/javascript/mastodon/features/link_timeline/index.tsx @@ -5,7 +5,8 @@ import { useParams } from 'react-router-dom'; import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; import { expandLinkTimeline } from 'mastodon/actions/timelines'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import StatusListContainer from 'mastodon/features/ui/containers/status_list_container'; import type { Card } from 'mastodon/models/status'; @@ -17,7 +18,7 @@ export const LinkTimeline: React.FC<{ const { url } = useParams<{ url: string }>(); const decodedUrl = url ? decodeURIComponent(url) : undefined; const dispatch = useAppDispatch(); - const columnRef = useRef(null); + const columnRef = useRef(null); const firstStatusId = useAppSelector((state) => decodedUrl ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access diff --git a/app/javascript/mastodon/features/lists/index.tsx b/app/javascript/mastodon/features/lists/index.tsx index cf413a1fe0..25a537336e 100644 --- a/app/javascript/mastodon/features/lists/index.tsx +++ b/app/javascript/mastodon/features/lists/index.tsx @@ -11,7 +11,7 @@ import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; import { fetchLists } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; diff --git a/app/javascript/mastodon/features/lists/members.tsx b/app/javascript/mastodon/features/lists/members.tsx index 184b54b92d..97b730f436 100644 --- a/app/javascript/mastodon/features/lists/members.tsx +++ b/app/javascript/mastodon/features/lists/members.tsx @@ -20,7 +20,7 @@ import { import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnSearchHeader } from 'mastodon/components/column_search_header'; import { FollowersCounter } from 'mastodon/components/counters'; diff --git a/app/javascript/mastodon/features/lists/new.tsx b/app/javascript/mastodon/features/lists/new.tsx index cf39331d7c..100f126c37 100644 --- a/app/javascript/mastodon/features/lists/new.tsx +++ b/app/javascript/mastodon/features/lists/new.tsx @@ -14,7 +14,7 @@ import { fetchList } from 'mastodon/actions/lists'; import { createList, updateList } from 'mastodon/actions/lists_typed'; import { apiGetAccounts } from 'mastodon/api/lists'; import type { RepliesPolicyType } from 'mastodon/api_types/lists'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 730d95bcd5..bb476fe51f 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -36,7 +36,8 @@ import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { submitMarkers } from '../../actions/markers'; -import Column from '../../components/column'; +import { Column } from '../../components/column'; +import type { ColumnRef } from '../../components/column'; import { ColumnHeader } from '../../components/column_header'; import { LoadGap } from '../../components/load_gap'; import ScrollableList from '../../components/scrollable_list'; @@ -96,7 +97,7 @@ export const Notifications: React.FC<{ selectNeedsNotificationPermission, ); - const columnRef = useRef(null); + const columnRef = useRef(null); const selectChild = useCallback((index: number, alignTop: boolean) => { const container = columnRef.current?.node as HTMLElement | undefined; diff --git a/app/javascript/mastodon/features/onboarding/follows.tsx b/app/javascript/mastodon/features/onboarding/follows.tsx index 02a8d01ba9..6ff0d31fd7 100644 --- a/app/javascript/mastodon/features/onboarding/follows.tsx +++ b/app/javascript/mastodon/features/onboarding/follows.tsx @@ -14,7 +14,7 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions'; import { markAsPartial } from 'mastodon/actions/timelines'; import { apiRequest } from 'mastodon/api'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnSearchHeader } from 'mastodon/components/column_search_header'; import ScrollableList from 'mastodon/components/scrollable_list'; diff --git a/app/javascript/mastodon/features/onboarding/profile.tsx b/app/javascript/mastodon/features/onboarding/profile.tsx index e4d9137e9e..1e5e868f18 100644 --- a/app/javascript/mastodon/features/onboarding/profile.tsx +++ b/app/javascript/mastodon/features/onboarding/profile.tsx @@ -13,7 +13,7 @@ import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import PersonIcon from '@/material-icons/400-24px/person.svg?react'; import { updateAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; diff --git a/app/javascript/mastodon/features/ui/components/column_loading.tsx b/app/javascript/mastodon/features/ui/components/column_loading.tsx index d9563dda7a..8b20e76ffb 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.tsx +++ b/app/javascript/mastodon/features/ui/components/column_loading.tsx @@ -1,4 +1,4 @@ -import Column from 'mastodon/components/column'; +import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import type { Props as ColumnHeaderProps } from 'mastodon/components/column_header'; From 2381ed55d7b62267101c1e64b9193caa897a0595 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Dec 2024 16:52:12 -0500 Subject: [PATCH 002/195] Add coverage for `Report#unresolved_siblings?` (#33141) --- spec/models/report_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index b4fbea437f..e62b2e9094 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -127,6 +127,28 @@ RSpec.describe Report do end end + describe '#unresolved_siblings?' do + subject { Fabricate :report } + + context 'when the target account has other unresolved reports' do + before { Fabricate :report, action_taken_at: nil, target_account: subject.target_account } + + it { is_expected.to be_unresolved_siblings } + end + + context 'when the target account has a resolved report' do + before { Fabricate :report, action_taken_at: 3.days.ago, target_account: subject.target_account } + + it { is_expected.to_not be_unresolved_siblings } + end + + context 'when the target account has no other reports' do + before { described_class.where(target_account: subject.target_account).destroy_all } + + it { is_expected.to_not be_unresolved_siblings } + end + end + describe 'validations' do let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } From be43b01eb11cc5bc134942f8d9725d20734349cf Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Dec 2024 16:52:17 -0500 Subject: [PATCH 003/195] Add coverage for `CustomFilter#expires_in` method (#33142) --- spec/models/custom_filter_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/models/custom_filter_spec.rb b/spec/models/custom_filter_spec.rb index afbc420241..18b791a73a 100644 --- a/spec/models/custom_filter_spec.rb +++ b/spec/models/custom_filter_spec.rb @@ -27,4 +27,28 @@ RSpec.describe CustomFilter do it { is_expected.to normalize(:context).from(['home', 'notifications', 'public ', '']).to(%w(home notifications public)) } end end + + describe '#expires_in' do + subject { custom_filter.expires_in } + + let(:custom_filter) { Fabricate.build(:custom_filter, expires_at: expires_at) } + + context 'when expires_at is nil' do + let(:expires_at) { nil } + + it { is_expected.to be_nil } + end + + context 'when expires is beyond the end of the range' do + let(:expires_at) { described_class::EXPIRATION_DURATIONS.last.from_now + 2.days } + + it { is_expected.to be_nil } + end + + context 'when expires is before the start of the range' do + let(:expires_at) { described_class::EXPIRATION_DURATIONS.first.from_now - 10.minutes } + + it { is_expected.to eq(described_class::EXPIRATION_DURATIONS.first) } + end + end end From 04b7046be1fd44baf304559e0c69087d101b358f Mon Sep 17 00:00:00 2001 From: Michael Stanclift Date: Tue, 3 Dec 2024 02:38:23 -0600 Subject: [PATCH 004/195] Provide option to force use of system scrollbar styling (#32117) --- app/helpers/application_helper.rb | 1 + .../styles/mastodon-light/diff.scss | 18 +++++----- app/javascript/styles/mastodon/reset.scss | 36 ++++++++++--------- app/models/concerns/user/has_settings.rb | 4 +++ app/models/user_settings.rb | 1 + .../preferences/appearance/show.html.haml | 1 + config/locales/simple_form.en.yml | 2 ++ 7 files changed, 38 insertions(+), 25 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9861ee7e8e..e1ca536c7d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -147,6 +147,7 @@ module ApplicationHelper output << content_for(:body_classes) output << "theme-#{current_theme.parameterize}" output << 'system-font' if current_account&.user&.setting_system_font_ui + output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion') output << 'rtl' if locale_direction == 'rtl' output.compact_blank.join(' ') diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index b19075139a..21da3575f5 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -523,6 +523,13 @@ a.sparkline { } } +.notification-group--annual-report { + .notification-group__icon, + .notification-group__main .link-button { + color: var(--indigo-3); + } +} + @supports not selector(::-webkit-scrollbar) { html { scrollbar-color: rgba($action-button-color, 0.25) @@ -530,13 +537,8 @@ a.sparkline { } } -::-webkit-scrollbar-thumb { - opacity: 0.25; -} - -.notification-group--annual-report { - .notification-group__icon, - .notification-group__main .link-button { - color: var(--indigo-3); +.custom-scrollbars { + ::-webkit-scrollbar-thumb { + opacity: 0.25; } } diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss index fc0305baf3..d1ca4a1837 100644 --- a/app/javascript/styles/mastodon/reset.scss +++ b/app/javascript/styles/mastodon/reset.scss @@ -59,24 +59,26 @@ table { } } -::-webkit-scrollbar { - width: 8px; - height: 8px; -} +.custom-scrollbars { + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } -::-webkit-scrollbar-thumb { - background-color: $action-button-color; - border: 2px var(--background-border-color); - border-radius: 12px; - width: 6px; - box-shadow: inset 0 0 0 2px var(--background-border-color); -} + ::-webkit-scrollbar-thumb { + background-color: $action-button-color; + border: 2px var(--background-border-color); + border-radius: 12px; + width: 6px; + box-shadow: inset 0 0 0 2px var(--background-border-color); + } -::-webkit-scrollbar-track { - background-color: var(--background-border-color); - border-radius: 0px; -} + ::-webkit-scrollbar-track { + background-color: var(--background-border-color); + border-radius: 0px; + } -::-webkit-scrollbar-corner { - background: transparent; + ::-webkit-scrollbar-corner { + background: transparent; + } } diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb index 1202f2033f..14d2f22c24 100644 --- a/app/models/concerns/user/has_settings.rb +++ b/app/models/concerns/user/has_settings.rb @@ -43,6 +43,10 @@ module User::HasSettings settings['web.use_system_font'] end + def setting_system_scrollbars_ui + settings['web.use_system_scrollbars'] + end + def setting_noindex settings['noindex'] end diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 0ae8ab8ec7..7db14407ac 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -24,6 +24,7 @@ class UserSettings setting :use_blurhash, default: true setting :use_pending_items, default: false setting :use_system_font, default: false + setting :use_system_scrollbars, default: false setting :disable_swiping, default: false setting :disable_hover_cards, default: false setting :delete_modal, default: true diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 1239b8bcd9..08cfea5a4f 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -59,6 +59,7 @@ = ff.input :'web.disable_swiping', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_swiping') = ff.input :'web.disable_hover_cards', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_hover_cards') = ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui') + = ff.input :'web.use_system_scrollbars', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_system_scrollbars_ui'), label: I18n.t('simple_form.labels.defaults.setting_system_scrollbars_ui') %h4= t 'appearance.discovery' diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index f451c780b0..b6b0481368 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -60,6 +60,7 @@ en: setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media + setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed username: You can use letters, numbers, and underscores @@ -223,6 +224,7 @@ en: setting_hide_network: Hide your social graph setting_reduce_motion: Reduce motion in animations setting_system_font_ui: Use system's default font + setting_system_scrollbars_ui: Use system's default scrollbar setting_theme: Site theme setting_trends: Show today's trends setting_unfollow_modal: Show confirmation dialog before unfollowing someone From ada5baf0b2a0d1a4ef538d5196168aeeb46b5f9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:39:54 +0000 Subject: [PATCH 005/195] Update dependency nokogiri to v1.16.8 (#33140) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index c35bf1a9a3..701ddd7821 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -426,7 +426,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.16.7) + nokogiri (1.16.8) mini_portile2 (~> 2.8.2) racc (~> 1.4) oj (3.16.7) From 88ff1c8e7e5ee2386afff57caee8aa10eb8a4f59 Mon Sep 17 00:00:00 2001 From: YOCKOW Date: Tue, 3 Dec 2024 17:46:11 +0900 Subject: [PATCH 006/195] Fix typo in CHANGELOG.md (#33145) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 240f6d1532..ebc166a48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -399,7 +399,7 @@ The following changelog entries focus on changes visible to users, administrator - Fix empty environment variables not using default nil value (#27400 by @renchap) - Fix language sorting in settings (#27158 by @gunchleoc) -## |4.2.11] - 2024-08-16 +## [4.2.11] - 2024-08-16 ### Added From 6cf87762a4fffd3d35c2e4c98900df5867be12eb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 3 Dec 2024 09:57:29 +0100 Subject: [PATCH 007/195] Change percentile label in year in review in web UI (#33148) --- .../mastodon/features/annual_report/percentile.tsx | 5 ++++- app/javascript/mastodon/locales/en.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/annual_report/percentile.tsx b/app/javascript/mastodon/features/annual_report/percentile.tsx index 22962019a1..0cfa7d18b9 100644 --- a/app/javascript/mastodon/features/annual_report/percentile.tsx +++ b/app/javascript/mastodon/features/annual_report/percentile.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-no-useless-fragment */ import { FormattedMessage, FormattedNumber } from 'react-intl'; +import { domain } from 'mastodon/initial_state'; import type { Percentiles } from 'mastodon/models/annual_report'; export const Percentile: React.FC<{ @@ -12,7 +13,7 @@ export const Percentile: React.FC<{
(
@@ -44,6 +45,8 @@ export const Percentile: React.FC<{ )}
), + + domain, }} > {(message) => <>{message}} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index a02eb7741b..4c7dcb57ec 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -103,7 +103,7 @@ "annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag", "annual_report.summary.most_used_hashtag.none": "None", "annual_report.summary.new_posts.new_posts": "new posts", - "annual_report.summary.percentile.text": "That puts you in the topof Mastodon users.", + "annual_report.summary.percentile.text": "That puts you in the topof {domain} users.", "annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.", "annual_report.summary.thanks": "Thanks for being part of Mastodon!", "attachments_list.unprocessed": "(unprocessed)", From 7135f513a45460742b87c7db88e3e280e3a7a060 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 3 Dec 2024 10:42:52 +0100 Subject: [PATCH 008/195] Add ability to search for all accounts when creating a list in web UI (#33036) --- app/javascript/mastodon/actions/alerts.js | 66 ----- app/javascript/mastodon/actions/alerts.ts | 90 +++++++ app/javascript/mastodon/api/accounts.ts | 13 + .../mastodon/components/account.jsx | 175 ------------- .../mastodon/components/account.tsx | 235 ++++++++++++++++++ .../mastodon/components/empty_account.tsx | 33 --- .../mastodon/components/server_banner.jsx | 2 +- .../mastodon/containers/account_container.jsx | 60 ----- .../mastodon/features/about/index.jsx | 2 +- .../mastodon/features/blocks/index.jsx | 4 +- .../compose/components/navigation_bar.jsx | 5 +- .../compose/components/search_results.jsx | 4 +- .../mastodon/features/explore/results.jsx | 2 +- .../mastodon/features/favourites/index.jsx | 4 +- .../mastodon/features/followers/index.jsx | 4 +- .../mastodon/features/following/index.jsx | 4 +- .../mastodon/features/lists/members.tsx | 54 +++- .../mastodon/features/mutes/index.jsx | 4 +- .../notifications/components/notification.jsx | 8 +- .../mastodon/features/onboarding/follows.tsx | 9 +- .../mastodon/features/reblogs/index.jsx | 4 +- .../confirmation_modals/follow_to_list.tsx | 43 ++++ .../components/confirmation_modals/index.ts | 1 + .../features/ui/components/modal_root.jsx | 2 + app/javascript/mastodon/locales/en.json | 5 +- 25 files changed, 459 insertions(+), 374 deletions(-) delete mode 100644 app/javascript/mastodon/actions/alerts.js create mode 100644 app/javascript/mastodon/actions/alerts.ts delete mode 100644 app/javascript/mastodon/components/account.jsx create mode 100644 app/javascript/mastodon/components/account.tsx delete mode 100644 app/javascript/mastodon/components/empty_account.tsx delete mode 100644 app/javascript/mastodon/containers/account_container.jsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_list.tsx diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js deleted file mode 100644 index 48dee2587f..0000000000 --- a/app/javascript/mastodon/actions/alerts.js +++ /dev/null @@ -1,66 +0,0 @@ -import { defineMessages } from 'react-intl'; - -import { AxiosError } from 'axios'; - -const messages = defineMessages({ - unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, - unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, - rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' }, - rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' }, -}); - -export const ALERT_SHOW = 'ALERT_SHOW'; -export const ALERT_DISMISS = 'ALERT_DISMISS'; -export const ALERT_CLEAR = 'ALERT_CLEAR'; -export const ALERT_NOOP = 'ALERT_NOOP'; - -export const dismissAlert = alert => ({ - type: ALERT_DISMISS, - alert, -}); - -export const clearAlert = () => ({ - type: ALERT_CLEAR, -}); - -export const showAlert = alert => ({ - type: ALERT_SHOW, - alert, -}); - -export const showAlertForError = (error, skipNotFound = false) => { - if (error.response) { - const { data, status, statusText, headers } = error.response; - - // Skip these errors as they are reflected in the UI - if (skipNotFound && (status === 404 || status === 410)) { - return { type: ALERT_NOOP }; - } - - // Rate limit errors - if (status === 429 && headers['x-ratelimit-reset']) { - return showAlert({ - title: messages.rateLimitedTitle, - message: messages.rateLimitedMessage, - values: { 'retry_time': new Date(headers['x-ratelimit-reset']) }, - }); - } - - return showAlert({ - title: `${status}`, - message: data.error || statusText, - }); - } - - // An aborted request, e.g. due to reloading the browser window, it not really error - if (error.code === AxiosError.ECONNABORTED) { - return { type: ALERT_NOOP }; - } - - console.error(error); - - return showAlert({ - title: messages.unexpectedTitle, - message: messages.unexpectedMessage, - }); -}; diff --git a/app/javascript/mastodon/actions/alerts.ts b/app/javascript/mastodon/actions/alerts.ts new file mode 100644 index 0000000000..a521f3ef35 --- /dev/null +++ b/app/javascript/mastodon/actions/alerts.ts @@ -0,0 +1,90 @@ +import { defineMessages } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; + +import { AxiosError } from 'axios'; +import type { AxiosResponse } from 'axios'; + +interface Alert { + title: string | MessageDescriptor; + message: string | MessageDescriptor; + values?: Record; +} + +interface ApiErrorResponse { + error?: string; +} + +const messages = defineMessages({ + unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, + unexpectedMessage: { + id: 'alert.unexpected.message', + defaultMessage: 'An unexpected error occurred.', + }, + rateLimitedTitle: { + id: 'alert.rate_limited.title', + defaultMessage: 'Rate limited', + }, + rateLimitedMessage: { + id: 'alert.rate_limited.message', + defaultMessage: 'Please retry after {retry_time, time, medium}.', + }, +}); + +export const ALERT_SHOW = 'ALERT_SHOW'; +export const ALERT_DISMISS = 'ALERT_DISMISS'; +export const ALERT_CLEAR = 'ALERT_CLEAR'; +export const ALERT_NOOP = 'ALERT_NOOP'; + +export const dismissAlert = (alert: Alert) => ({ + type: ALERT_DISMISS, + alert, +}); + +export const clearAlert = () => ({ + type: ALERT_CLEAR, +}); + +export const showAlert = (alert: Alert) => ({ + type: ALERT_SHOW, + alert, +}); + +export const showAlertForError = (error: unknown, skipNotFound = false) => { + if (error instanceof AxiosError && error.response) { + const { status, statusText, headers } = error.response; + const { data } = error.response as AxiosResponse; + + // Skip these errors as they are reflected in the UI + if (skipNotFound && (status === 404 || status === 410)) { + return { type: ALERT_NOOP }; + } + + // Rate limit errors + if (status === 429 && headers['x-ratelimit-reset']) { + return showAlert({ + title: messages.rateLimitedTitle, + message: messages.rateLimitedMessage, + values: { + retry_time: new Date(headers['x-ratelimit-reset'] as string), + }, + }); + } + + return showAlert({ + title: `${status}`, + message: data.error ?? statusText, + }); + } + + // An aborted request, e.g. due to reloading the browser window, it not really error + if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) { + return { type: ALERT_NOOP }; + } + + console.error(error); + + return showAlert({ + title: messages.unexpectedTitle, + message: messages.unexpectedMessage, + }); +}; diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index bd1757e827..717010ba74 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -5,3 +5,16 @@ export const apiSubmitAccountNote = (id: string, value: string) => apiRequestPost(`v1/accounts/${id}/note`, { comment: value, }); + +export const apiFollowAccount = ( + id: string, + params?: { + reblogs: boolean; + }, +) => + apiRequestPost(`v1/accounts/${id}/follow`, { + ...params, + }); + +export const apiUnfollowAccount = (id: string) => + apiRequestPost(`v1/accounts/${id}/unfollow`); diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx deleted file mode 100644 index fa66fd56bb..0000000000 --- a/app/javascript/mastodon/components/account.jsx +++ /dev/null @@ -1,175 +0,0 @@ -import PropTypes from 'prop-types'; -import { useCallback } from 'react'; - -import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; - -import classNames from 'classnames'; -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; - -import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; -import { EmptyAccount } from 'mastodon/components/empty_account'; -import { FollowButton } from 'mastodon/components/follow_button'; -import { ShortNumber } from 'mastodon/components/short_number'; -import { VerifiedBadge } from 'mastodon/components/verified_badge'; - -import DropdownMenuContainer from '../containers/dropdown_menu_container'; -import { me } from '../initial_state'; - -import { Avatar } from './avatar'; -import { Button } from './button'; -import { FollowersCounter } from './counters'; -import { DisplayName } from './display_name'; -import { RelativeTimestamp } from './relative_timestamp'; - -const messages = defineMessages({ - unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, - unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, - mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' }, - unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' }, - mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, - block: { id: 'account.block_short', defaultMessage: 'Block' }, - more: { id: 'status.more', defaultMessage: 'More' }, -}); - -const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => { - const intl = useIntl(); - - const handleBlock = useCallback(() => { - onBlock(account); - }, [onBlock, account]); - - const handleMute = useCallback(() => { - onMute(account); - }, [onMute, account]); - - const handleMuteNotifications = useCallback(() => { - onMuteNotifications(account, true); - }, [onMuteNotifications, account]); - - const handleUnmuteNotifications = useCallback(() => { - onMuteNotifications(account, false); - }, [onMuteNotifications, account]); - - if (!account) { - return ; - } - - if (hidden) { - return ( - <> - {account.get('display_name')} - {account.get('username')} - - ); - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = ; - } else if (blocking) { - buttons =
); @@ -167,7 +167,7 @@ class Notification extends ImmutablePureComponent { -