diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index f44cf79730..f0d583bc54 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -9,6 +9,10 @@ module FormattingHelper TextFormatter.new(text, options).to_s end + def url_for_preview_card(preview_card) + preview_card.url + end + def extract_status_plain_text(status) PlainTextFormatter.new(status.text, status.local?).to_s end diff --git a/app/javascript/mastodon/components/__tests__/button-test.jsx b/app/javascript/mastodon/components/__tests__/button-test.jsx index 6de961f784..ad7a0c49ca 100644 --- a/app/javascript/mastodon/components/__tests__/button-test.jsx +++ b/app/javascript/mastodon/components/__tests__/button-test.jsx @@ -1,7 +1,7 @@ import { render, fireEvent, screen } from '@testing-library/react'; import renderer from 'react-test-renderer'; -import Button from '../button'; +import { Button } from '../button'; describe(' - ); - } - -} diff --git a/app/javascript/mastodon/components/button.tsx b/app/javascript/mastodon/components/button.tsx new file mode 100644 index 0000000000..0b6a0f267e --- /dev/null +++ b/app/javascript/mastodon/components/button.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; + +import classNames from 'classnames'; + +interface BaseProps extends React.ButtonHTMLAttributes { + block?: boolean; + secondary?: boolean; + text?: JSX.Element; +} + +interface PropsWithChildren extends BaseProps { + text?: never; +} + +interface PropsWithText extends BaseProps { + text: JSX.Element; + children: never; +} + +type Props = PropsWithText | PropsWithChildren; + +export const Button: React.FC = ({ + text, + type = 'button', + onClick, + disabled, + block, + secondary, + className, + title, + children, + ...props +}) => { + const handleClick = useCallback>( + (e) => { + if (!disabled && onClick) { + onClick(e); + } + }, + [disabled, onClick], + ); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index a378868020..e10c31b02d 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -11,7 +11,7 @@ import { HotKeys } from 'react-hotkeys'; import { Icon } from 'mastodon/components/icon'; import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; -import { withOptionalRouter, WithRouterPropTypes } from 'mastodon/utils/react_router'; +import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router'; import Card from '../features/status/components/card'; // We use the component (and not the container) since we do not want @@ -113,7 +113,7 @@ class Status extends ImmutablePureComponent { inUse: PropTypes.bool, available: PropTypes.bool, }), - ...WithRouterPropTypes, + ...WithOptionalRouterPropTypes, }; // Avoid checking props that are functions (and whose equality will always diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 7183f7af90..9425b25b71 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { Avatar } from 'mastodon/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx index 4872455e97..59b7358233 100644 --- a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx @@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { revealAccount } from 'mastodon/actions/accounts'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { domain } from 'mastodon/initial_state'; const mapDispatchToProps = (dispatch, { accountId }) => ({ diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 030f7df953..5d99a5eedb 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -14,7 +14,7 @@ import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/ import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; -import Button from '../../../components/button'; +import { Button } from '../../../components/button'; import { maxChars } from '../../../initial_state'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container'; diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx index 0306b63d32..a5e5717b32 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.jsx +++ b/app/javascript/mastodon/features/directory/components/account_card.jsx @@ -17,7 +17,7 @@ import { } from 'mastodon/actions/accounts'; import { openModal } from 'mastodon/actions/modal'; import { Avatar } from 'mastodon/components/avatar'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { DisplayName } from 'mastodon/components/display_name'; import { ShortNumber } from 'mastodon/components/short_number'; import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/explore/statuses.jsx b/app/javascript/mastodon/features/explore/statuses.jsx index f32a4a5368..0d8d212b25 100644 --- a/app/javascript/mastodon/features/explore/statuses.jsx +++ b/app/javascript/mastodon/features/explore/statuses.jsx @@ -45,24 +45,20 @@ class Statuses extends PureComponent { const emptyMessage = ; return ( - <> - - - - - - + } + alwaysPrepend + timelineId='explore' + statusIds={statusIds} + scrollKey='explore-statuses' + hasMore={hasMore} + isLoading={isLoading} + onLoadMore={this.handleLoadMore} + emptyMessage={emptyMessage} + bindToDocument={!multiColumn} + withCounters + /> ); } diff --git a/app/javascript/mastodon/features/filters/added_to_filter.jsx b/app/javascript/mastodon/features/filters/added_to_filter.jsx index ec848f622e..e341eddbad 100644 --- a/app/javascript/mastodon/features/filters/added_to_filter.jsx +++ b/app/javascript/mastodon/features/filters/added_to_filter.jsx @@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { toServerSideType } from 'mastodon/utils/filters'; const mapStateToProps = (state, { filterId }) => ({ diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx index 46050309ff..d2fe8851f6 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.jsx @@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { ShortNumber } from 'mastodon/components/short_number'; const messages = defineMessages({ @@ -76,4 +76,4 @@ HashtagHeader.propTypes = { disabled: PropTypes.bool, onClick: PropTypes.func, intl: PropTypes.object, -}; \ No newline at end of file +}; diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx index 69bbd8cd87..d79a3d4154 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.jsx +++ b/app/javascript/mastodon/features/interaction_modal/index.jsx @@ -11,7 +11,7 @@ import { throttle, escapeRegExp } from 'lodash'; import { openModal, closeModal } from 'mastodon/actions/modal'; import api from 'mastodon/api'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { Icon } from 'mastodon/components/icon'; import { registrationsOpen, sso_redirect } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/lists/components/new_list_form.jsx b/app/javascript/mastodon/features/lists/components/new_list_form.jsx index 40e2d4a1c2..0fed9d70a2 100644 --- a/app/javascript/mastodon/features/lists/components/new_list_form.jsx +++ b/app/javascript/mastodon/features/lists/components/new_list_form.jsx @@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; const messages = defineMessages({ label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' }, diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx index 59b0335c10..9ce014d258 100644 --- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx +++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import { requestBrowserPermission } from 'mastodon/actions/notifications'; import { changeSetting } from 'mastodon/actions/settings'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; diff --git a/app/javascript/mastodon/features/report/category.jsx b/app/javascript/mastodon/features/report/category.jsx index fb9e55c579..a2fc3b23b4 100644 --- a/app/javascript/mastodon/features/report/category.jsx +++ b/app/javascript/mastodon/features/report/category.jsx @@ -7,7 +7,7 @@ import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import Option from './components/option'; diff --git a/app/javascript/mastodon/features/report/comment.jsx b/app/javascript/mastodon/features/report/comment.jsx index ca9ea9d268..ec59746923 100644 --- a/app/javascript/mastodon/features/report/comment.jsx +++ b/app/javascript/mastodon/features/report/comment.jsx @@ -11,7 +11,7 @@ import { createSelector } from 'reselect'; import Toggle from 'react-toggle'; import { fetchAccount } from 'mastodon/actions/accounts'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/report/rules.jsx b/app/javascript/mastodon/features/report/rules.jsx index 67d92839ed..621f140adb 100644 --- a/app/javascript/mastodon/features/report/rules.jsx +++ b/app/javascript/mastodon/features/report/rules.jsx @@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import Option from './components/option'; diff --git a/app/javascript/mastodon/features/report/statuses.jsx b/app/javascript/mastodon/features/report/statuses.jsx index 4ba8d6065f..5e416f1e1a 100644 --- a/app/javascript/mastodon/features/report/statuses.jsx +++ b/app/javascript/mastodon/features/report/statuses.jsx @@ -7,7 +7,7 @@ import { OrderedSet } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container'; diff --git a/app/javascript/mastodon/features/report/thanks.jsx b/app/javascript/mastodon/features/report/thanks.jsx index 79ddc2e802..146d4b3897 100644 --- a/app/javascript/mastodon/features/report/thanks.jsx +++ b/app/javascript/mastodon/features/report/thanks.jsx @@ -11,7 +11,7 @@ import { muteAccount, blockAccount, } from 'mastodon/actions/accounts'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; const mapStateToProps = () => ({}); diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx index eb128f68e7..003f50183d 100644 --- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx @@ -9,7 +9,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { followAccount } from 'mastodon/actions/accounts'; -import Button from 'mastodon/components/button'; +import { Button } from 'mastodon/components/button'; import { IconButton } from 'mastodon/components/icon_button'; import Option from 'mastodon/features/report/components/option'; import { languages as preloadedLanguages } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/ui/components/block_modal.jsx b/app/javascript/mastodon/features/ui/components/block_modal.jsx index 7cfd252e0b..cfac692324 100644 --- a/app/javascript/mastodon/features/ui/components/block_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/block_modal.jsx @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { blockAccount } from '../../../actions/accounts'; import { closeModal } from '../../../actions/modal'; import { initReport } from '../../../actions/reports'; -import Button from '../../../components/button'; +import { Button } from '../../../components/button'; import { makeGetAccount } from '../../../selectors'; const makeMapStateToProps = () => { @@ -51,10 +51,6 @@ class BlockModal extends PureComponent { intl: PropTypes.object.isRequired, }; - componentDidMount() { - this.button.focus(); - } - handleClick = () => { this.props.onClose(); this.props.onConfirm(this.props.account); @@ -69,10 +65,6 @@ class BlockModal extends PureComponent { this.props.onClose(); }; - setRef = (c) => { - this.button = c; - }; - render () { const { account } = this.props; @@ -95,7 +87,7 @@ class BlockModal extends PureComponent { - diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index 9944bb1644..91a77f883d 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -16,7 +16,7 @@ import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdo import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { Avatar } from '../../../components/avatar'; -import Button from '../../../components/button'; +import { Button } from '../../../components/button'; import { DisplayName } from '../../../components/display_name'; import { RelativeTimestamp } from '../../../components/relative_timestamp'; import StatusContent from '../../../components/status_content'; @@ -55,10 +55,6 @@ class BoostModal extends ImmutablePureComponent { ...WithRouterPropTypes, }; - componentDidMount() { - this.button.focus(); - } - handleReblog = () => { this.props.onReblog(this.props.status, this.props.privacy); this.props.onClose(); @@ -76,10 +72,6 @@ class BoostModal extends ImmutablePureComponent { return document.getElementsByClassName('modal-root__container')[0]; }; - setRef = (c) => { - this.button = c; - }; - render () { const { status, privacy, intl } = this.props; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; @@ -133,7 +125,7 @@ class BoostModal extends ImmutablePureComponent { onChange={this.props.onChangeBoostPrivacy} /> )} - - diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index dbd0a3f3a2..12365c879d 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -163,13 +163,13 @@ "confirmation_modal.cancel": "Annulearje", "confirmations.block.block_and_report": "Blokkearje en rapportearje", "confirmations.block.confirm": "Blokkearje", - "confirmations.block.message": "Bisto wis datsto {name} blokkearje wolst?", + "confirmations.block.message": "Binne jo wis dat jo {name} blokkearje wolle?", "confirmations.cancel_follow_request.confirm": "Fersyk annulearje", "confirmations.cancel_follow_request.message": "Binne jo wis dat jo jo fersyk om {name} te folgjen annulearje wolle?", "confirmations.delete.confirm": "Fuortsmite", "confirmations.delete.message": "Binne jo wis dat jo dit berjocht fuortsmite wolle?", "confirmations.delete_list.confirm": "Fuortsmite", - "confirmations.delete_list.message": "Bisto wis datsto dizze list foar permanint fuortsmite wolst?", + "confirmations.delete_list.message": "Binne jo wis dat jo dizze list foar permanint fuortsmite wolle?", "confirmations.discard_edit_media.confirm": "Fuortsmite", "confirmations.discard_edit_media.message": "Jo hawwe net-bewarre wizigingen yn de mediabeskriuwing of foarfertoaning, wolle jo dizze dochs fuortsmite?", "confirmations.domain_block.confirm": "Alles fan dit domein blokkearje", @@ -177,16 +177,16 @@ "confirmations.edit.confirm": "Bewurkje", "confirmations.edit.message": "Troch no te bewurkjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?", "confirmations.logout.confirm": "Ofmelde", - "confirmations.logout.message": "Bisto wis datsto ôfmelde wolst?", + "confirmations.logout.message": "Binne jo wis dat jo ôfmelde wolle?", "confirmations.mute.confirm": "Negearje", - "confirmations.mute.explanation": "Dit sil berjochten fan harren en berjochten dêr’t se yn fermeld wurde ûnsichtber meitsje, mar se sille berjochten noch hieltyd sjen kinne en jo folgje kinne.", + "confirmations.mute.explanation": "Dit sil berjochten fan harren en berjochten dêr’t se yn fermeld wurde ûnsichtber meitsje, mar se sille jo berjochten noch hieltyd sjen kinne en jo folgje kinne.", "confirmations.mute.message": "Binne jo wis dat jo {name} negearje wolle?", "confirmations.redraft.confirm": "Fuortsmite en opnij opstelle", "confirmations.redraft.message": "Binne jo wis dat jo dit berjocht fuortsmite en opnij opstelle wolle? Favoriten en boosts geane dan ferlern en reaksjes op it oarspronklike berjocht reitsje jo kwyt.", "confirmations.reply.confirm": "Reagearje", "confirmations.reply.message": "Troch no te reagearjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?", "confirmations.unfollow.confirm": "Net mear folgje", - "confirmations.unfollow.message": "Bisto wis datsto {name} net mear folgje wolst?", + "confirmations.unfollow.message": "Binne jo wis dat jo {name} net mear folgje wolle?", "conversation.delete": "Petear fuortsmite", "conversation.mark_as_read": "As lêzen markearje", "conversation.open": "Petear toane", @@ -351,7 +351,7 @@ "keyboard_shortcuts.local": "to open local timeline", "keyboard_shortcuts.mention": "Skriuwer fermelde", "keyboard_shortcuts.muted": "to open muted users list", - "keyboard_shortcuts.my_profile": "Dyn profyl iepenje", + "keyboard_shortcuts.my_profile": "Jo profyl iepenje", "keyboard_shortcuts.notifications": "Meldingen toane", "keyboard_shortcuts.open_media": "Media iepenje", "keyboard_shortcuts.pinned": "Fêstsette berjochten toane", @@ -421,20 +421,20 @@ "navigation_bar.public_timeline": "Globale tiidline", "navigation_bar.search": "Sykje", "navigation_bar.security": "Befeiliging", - "not_signed_in_indicator.not_signed_in": "Do moatst oanmelde om tagong ta dizze ynformaasje te krijen.", + "not_signed_in_indicator.not_signed_in": "Jo moatte oanmelde om tagong ta dizze ynformaasje te krijen.", "notification.admin.report": "{name} hat {target} rapportearre", "notification.admin.sign_up": "{name} hat harren registrearre", "notification.favourite": "{name} hat jo berjocht as favoryt markearre", "notification.follow": "{name} folget dy", "notification.follow_request": "{name} hat dy in folchfersyk stjoerd", "notification.mention": "{name} hat dy fermeld", - "notification.own_poll": "Dyn poll is beëinige", + "notification.own_poll": "Jo poll is beëinige", "notification.poll": "In enkête dêr’t jo yn stimd hawwe is beëinige", "notification.reblog": "{name} hat jo berjocht boost", "notification.status": "{name} hat in berjocht pleatst", "notification.update": "{name} hat in berjocht bewurke", "notifications.clear": "Meldingen wiskje", - "notifications.clear_confirmation": "Bisto wis datsto al dyn meldingen permanint fuortsmite wolst?", + "notifications.clear_confirmation": "Binne jo wis dat jo al jo meldingen permanint fuortsmite wolle?", "notifications.column_settings.admin.report": "Nije rapportaazjes:", "notifications.column_settings.admin.sign_up": "Nije registraasjes:", "notifications.column_settings.alert": "Desktopmeldingen", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index f2237d693e..09c95e02cd 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -204,7 +204,7 @@ "dismissable_banner.explore_links": "이 소식들은 오늘 소셜 웹에서 가장 많이 공유된 내용들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.", "dismissable_banner.explore_statuses": "이 게시물들은 오늘 소셜 웹에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.", "dismissable_banner.explore_tags": "이 해시태그들은 이 서버와 분산화된 네트워크의 다른 서버에서 사람들의 인기를 끌고 있는 것들입니다.", - "dismissable_banner.public_timeline": "이것들은 {domain}에 있는 사람들이 팔로우한 사람들의 최신 게시물들입니다.", + "dismissable_banner.public_timeline": "{domain} 사람들이 팔로우하는 소셜 웹 사람들의 최신 공개 게시물입니다.", "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.", "embed.preview": "이렇게 표시됩니다:", "emoji_button.activity": "활동", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 0a14ea93a3..8ff390aabb 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -153,7 +153,7 @@ "compose_form.publish": "Legg ut", "compose_form.publish_form": "Legg ut", "compose_form.publish_loud": "{publish}!", - "compose_form.save_changes": "Gøym", + "compose_form.save_changes": "Lagre endringar", "compose_form.sensitive.hide": "{count, plural, one {Marker mediet som ømtolig} other {Marker media som ømtolige}}", "compose_form.sensitive.marked": "{count, plural, one {Mediet er markert som ømtolig} other {Media er markerte som ømtolige}}", "compose_form.sensitive.unmarked": "{count, plural, one {Mediet er ikkje markert som ømtolig} other {Media er ikkje markerte som ømtolige}}", @@ -171,7 +171,7 @@ "confirmations.delete_list.confirm": "Slett", "confirmations.delete_list.message": "Er du sikker på at du vil sletta denne lista for alltid?", "confirmations.discard_edit_media.confirm": "Forkast", - "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkaste dei likevel?", + "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkasta dei likevel?", "confirmations.domain_block.confirm": "Skjul alt frå domenet", "confirmations.domain_block.message": "Er du heilt, heilt sikker på at du vil skjula heile {domain}? I dei fleste tilfelle er det godt nok og føretrekt med nokre få målretta blokkeringar eller målbindingar. Du kjem ikkje til å sjå innhald frå domenet i fødererte tidsliner eller i varsla dine. Fylgjarane dine frå domenet vert fjerna.", "confirmations.edit.confirm": "Rediger", @@ -285,7 +285,7 @@ "footer.privacy_policy": "Personvernsreglar", "footer.source_code": "Vis kjeldekode", "footer.status": "Status", - "generic.saved": "Gøymt", + "generic.saved": "Lagra", "getting_started.heading": "Kom i gang", "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "eller {additional}", @@ -314,7 +314,7 @@ "home.pending_critical_update.link": "Sjå oppdateringar", "home.pending_critical_update.title": "Kritisk sikkerheitsoppdatering er tilgjengeleg!", "home.show_announcements": "Vis kunngjeringar", - "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerkja dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.", + "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerka dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.", "interaction_modal.description.follow": "Med ein konto på Mastodon kan du fylgja {name} for å sjå innlegga deira i din heimestraum.", "interaction_modal.description.reblog": "Med ein konto på Mastodon kan du framheva dette innlegget for å dela det med dine eigne fylgjarar.", "interaction_modal.description.reply": "Med ein konto på Mastodon kan du svara på dette innlegget.", @@ -673,7 +673,7 @@ "status.unmute_conversation": "Opphev målbinding av samtalen", "status.unpin": "Løys frå profil", "subscribed_languages.lead": "Kun innlegg på valde språk vil bli dukke opp i heimestraumen din og i listene dine etter denne endringa. For å motta innlegg på alle språk, la vere å velje nokon.", - "subscribed_languages.save": "Gøym", + "subscribed_languages.save": "Lagre endringar", "subscribed_languages.target": "Endre abonnerte språk for {target}", "tabs_bar.home": "Heim", "tabs_bar.notifications": "Varsel", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 130b28bc74..ec2c76e8f9 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -303,7 +303,7 @@ "hashtag.unfollow": "Отпрати хеш ознаку", "hashtags.and_other": "…и {count, plural, one {још #} few {још #}other {још #}}", "home.actions.go_to_explore": "Погледате шта је у тренду", - "home.actions.go_to_suggestions": "Пронађeте људе које бисте пратили", + "home.actions.go_to_suggestions": "Пронађете људе које бисте пратили", "home.column_settings.basic": "Основна", "home.column_settings.show_reblogs": "Прикажи подржавања", "home.column_settings.show_replies": "Прикажи одговоре", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index f8dcb6a89f..93db5c9e2e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -114,7 +114,7 @@ "column.directory": "瀏覽個人檔案", "column.domain_blocks": "已封鎖網域", "column.favourites": "最愛", - "column.firehose": "即時河道", + "column.firehose": "即時內容", "column.follow_requests": "跟隨請求", "column.home": "首頁", "column.lists": "列表", @@ -629,7 +629,7 @@ "status.edit": "編輯", "status.edited": "編輯於 {date}", "status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}", - "status.embed": "內嵌", + "status.embed": "內嵌嘟文", "status.favourite": "最愛", "status.filter": "過濾此嘟文", "status.filtered": "已過濾", diff --git a/app/javascript/mastodon/utils/react_router.jsx b/app/javascript/mastodon/utils/react_router.jsx index a56883270b..fa8f0db2b5 100644 --- a/app/javascript/mastodon/utils/react_router.jsx +++ b/app/javascript/mastodon/utils/react_router.jsx @@ -49,6 +49,7 @@ export function withOptionalRouter(Component) { C.displayName = displayName; C.WrappedComponent = Component; C.propTypes = { + ...Component.propTypes, wrappedComponentRef: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, diff --git a/app/lib/activitypub/linked_data_signature.rb b/app/lib/activitypub/linked_data_signature.rb index ea59879f3b..faea63e8f1 100644 --- a/app/lib/activitypub/linked_data_signature.rb +++ b/app/lib/activitypub/linked_data_signature.rb @@ -18,8 +18,8 @@ class ActivityPub::LinkedDataSignature return unless type == 'RsaSignature2017' - creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri) - creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) + creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri) + creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) if creator&.public_key.blank? return if creator.nil? @@ -28,6 +28,8 @@ class ActivityPub::LinkedDataSignature to_be_verified = options_hash + document_hash creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified) + rescue OpenSSL::PKey::RSAError + false end def sign!(creator, sign_with: nil) diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 4e24fab240..a1751c426d 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -55,7 +55,7 @@ class PreviewCard < ApplicationRecord has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false - validates :url, presence: true, uniqueness: true + validates :url, presence: true, uniqueness: true, url: true validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES validates_attachment_size :image, less_than: LIMIT remotable_attachment :image, LIMIT diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml index 1ca3483715..ee3774790c 100644 --- a/app/views/admin/trends/links/_preview_card.html.haml +++ b/app/views/admin/trends/links/_preview_card.html.haml @@ -4,7 +4,7 @@ .batch-table__row__content.pending-account .pending-account__header - = link_to preview_card.title, preview_card.url + = link_to preview_card.title, url_for_preview_card(preview_card) %br/ diff --git a/config/brakeman.ignore b/config/brakeman.ignore deleted file mode 100644 index d5c0b94436..0000000000 --- a/config/brakeman.ignore +++ /dev/null @@ -1,39 +0,0 @@ -{ - "ignored_warnings": [ - { - "warning_type": "Cross-Site Scripting", - "warning_code": 4, - "fingerprint": "cd5cfd7f40037fbfa753e494d7129df16e358bfc43ef0da3febafbf4ee1ed3ac", - "check_name": "LinkToHref", - "message": "Potentially unsafe model attribute in `link_to` href", - "file": "app/views/admin/trends/links/_preview_card.html.haml", - "line": 7, - "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", - "code": "link_to((Unresolved Model).new.title, (Unresolved Model).new.url)", - "render_path": [ - { - "type": "template", - "name": "admin/trends/links/index", - "line": 49, - "file": "app/views/admin/trends/links/index.html.haml", - "rendered": { - "name": "admin/trends/links/_preview_card", - "file": "app/views/admin/trends/links/_preview_card.html.haml" - } - } - ], - "location": { - "type": "template", - "template": "admin/trends/links/_preview_card" - }, - "user_input": "(Unresolved Model).new.url", - "confidence": "Weak", - "cwe_id": [ - 79 - ], - "note": "" - } - ], - "updated": "2023-07-12 11:20:51 -0400", - "brakeman_version": "6.0.0" -} diff --git a/config/brakeman.yml b/config/brakeman.yml index 95ebc13865..9ac95c8006 100644 --- a/config/brakeman.yml +++ b/config/brakeman.yml @@ -1,3 +1,5 @@ --- :skip_checks: - CheckPermitAttributes +:url_safe_methods: + - url_for_preview_card diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml index a52e7cff6f..c510a58d14 100644 --- a/config/locales/activerecord.zh-CN.yml +++ b/config/locales/activerecord.zh-CN.yml @@ -36,7 +36,7 @@ zh-CN: status: attributes: reblog: - taken: 已被转嘟过 + taken: 已经被转嘟过 user: attributes: email: diff --git a/config/locales/devise.fy.yml b/config/locales/devise.fy.yml index 9e93d2c19f..910d2d3094 100644 --- a/config/locales/devise.fy.yml +++ b/config/locales/devise.fy.yml @@ -13,7 +13,7 @@ fy: locked: Jo account is blokkearre. not_found_in_database: "%{authentication_keys} of wachtwurd ûnjildich." pending: Jo account moat noch hieltyd beoardiele wurde. - timeout: Dyn sesje is ferrûn, meld dy opnij oan. + timeout: Jo sesje is ferrûn. Meld jo opnij oan om troch te gean. unauthenticated: Jo moatte oanmelde of registrearje. unconfirmed: Jo moatte earst jo account befêstigje. mailer: diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml index 5a3dbff7e4..e46c433530 100644 --- a/config/locales/devise.zh-CN.yml +++ b/config/locales/devise.zh-CN.yml @@ -9,7 +9,7 @@ zh-CN: already_authenticated: 你已登录。 inactive: 你还没有激活账户。 invalid: "%{authentication_keys} 无效或密码错误。" - last_attempt: 你只有最后一次尝试机会,若未通过,账号将被锁定。 + last_attempt: 你只有最后一次尝试机会,若未通过,帐号将被锁定。 locked: 你的账户已被锁定。 not_found_in_database: "%{authentication_keys}或密码错误。" pending: 你的账号仍在审核中。 @@ -85,7 +85,7 @@ zh-CN: send_instructions: 如果你的电子邮件地址存在于我们的数据库中,你将在几分钟后收到一个密码恢复链接。如果你没有收到这封邮件,请检查你邮箱的垃圾箱。 send_paranoid_instructions: 如果你的电子邮件地址存在于我们的数据库中,你将在几分钟后收到一个密码恢复链接。如果你没有收到这封邮件,请检查你邮箱的垃圾箱。 updated: 你的密码已成功修改,现在你已登录。 - updated_not_active: 你的密码已成功修改。 + updated_not_active: 你的密码已修改成功。 registrations: destroyed: 再见!你的账户已成功注销。我们希望很快可以再见到你。 signed_up: 欢迎!你已成功注册。 diff --git a/config/locales/doorkeeper.fy.yml b/config/locales/doorkeeper.fy.yml index 8acf7ea9b8..a43defc427 100644 --- a/config/locales/doorkeeper.fy.yml +++ b/config/locales/doorkeeper.fy.yml @@ -43,7 +43,7 @@ fy: new: Nije tapassing scopes: Tastimmingen show: Toane - title: Dyn tapassingen + title: Jo tapassingen new: title: Nije tapassing show: @@ -60,7 +60,7 @@ fy: error: title: Der is in flater bard new: - prompt_html: "%{client_name} hat tastimming nedich om tagong te krijen ta dyn account. It giet om in tapassing fan in tredde partij.Asto dit net fertroust, moatsto gjin tastimming jaan." + prompt_html: "%{client_name} hat tastimming nedich om tagong te krijen ta jo account. It giet om in tapassing fan in tredde partij.As jo dit net fertrouwe, moatte jo gjin tastimming jaan." review_permissions: Tastimmingen beoardiele title: Autorisaasje fereaske show: @@ -69,15 +69,15 @@ fy: buttons: revoke: Ynlûke confirmations: - revoke: Bisto wis? + revoke: Binne jo wis? index: authorized_at: Autorisearre op %{date} - description_html: Dit binne tapassingen dy’t tagong hawwe ta dyn account fia de API. As der tapassingen tusken steane dy’tsto net werkenst of in tapassing harren misdraacht, kinsto de tagongsrjochten fan de tapassing ynlûke. + description_html: Dit binne tapassingen dy’t fia de API tagong hawwe ta jo account. As der tapassingen tusken steane dy’t jo net werkenne of in tapassing harren misdraacht, kinne jo de tagongsrjochten fan de tapassing ynlûke. last_used_at: Lêst brûkt op %{date} never_used: Nea brûkt scopes: Tastimmingen superapp: Yntern - title: Dyn autorisearre tapassingen + title: Jo autorisearre tapassingen errors: messages: access_denied: De boarne-eigener of autorisaasjeserver hat it fersyk wegere. @@ -165,22 +165,22 @@ fy: admin:write:reports: moderaasjemaatregelen nimme yn rapportaazjes crypto: ein-ta-ein-fersifering brûke follow: relaasjes tusken accounts bewurkje - push: dyn pushmeldingen ûntfange - read: alle gegevens fan dyn account lêze + push: jo pushmeldingen ûntfange + read: alle gegevens fan jo account lêze read:accounts: accountynformaasje besjen - read:blocks: dyn blokkearre brûkers besjen - read:bookmarks: dyn blêdwizers besjen + read:blocks: jo blokkearre brûkers besjen + read:bookmarks: jo blêdwizers besjen read:favourites: jo favoriten besjen - read:filters: dyn filters besjen + read:filters: jo filters besjen read:follows: de accounts dy’tsto folgest besjen - read:lists: dyn listen besjen - read:mutes: dyn negearre brûkers besjen - read:notifications: dyn meldingen besjen - read:reports: dyn rapportearre berjochten besjen - read:search: út dyn namme sykje + read:lists: jo listen besjen + read:mutes: jo negearre brûkers besjen + read:notifications: jo meldingen besjen + read:reports: jo rapportearre berjochten besjen + read:search: út jo namme sykje read:statuses: alle berjochten besjen - write: alle gegevens fan dyn account bewurkje - write:accounts: dyn profyl bewurkje + write: alle gegevens fan jo account bewurkje + write:accounts: jo profyl bewurkje write:blocks: accounts en domeinen blokkearje write:bookmarks: berjochten oan blêdwizers tafoegje write:conversations: petearen negearre en fuortsmite diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 9f34e56216..1c187c1de1 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -570,7 +570,7 @@ nn: enabled: Skrudd på inbox_url: Overførings-URL pending: Avventer overgangens godkjenning - save_and_enable: Lagr og slå på + save_and_enable: Lagre og slå på setup: Sett opp en overgangsforbindelse signatures_not_enabled: Overgangar fungerer ikkje så lenge sikker- eller kvitlistingsmodus er aktivert status: Status @@ -1280,7 +1280,7 @@ nn: deselect: Vel ingen none: Ingen order_by: Sorter etter - save_changes: Lagr endringar + save_changes: Lagre endringar select_all_matching_items: one: Vel %{count} element som passar til søket ditt. other: Vel %{count} element som passar til søket ditt. diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index 474d858382..f506b79252 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -86,7 +86,7 @@ fy: media_cache_retention_period: Mediabestannen dy’t fan oare servers download binne wurde nei it opjûne oantal dagen fuortsmiten en wurde op fersyk opnij download. peers_api_enabled: In list mei domeinnammen, dêr’t dizze server yn fediverse kontakt hân mei hat. Hjir wurdt gjin data dield, oft jo mei in bepaalde server federearrest, mar alinnich, dat jo server dat wit. Dit wurdt foar tsjinsten brûkt, dy’t statistiken oer federaasje yn algemiene sin sammelet. profile_directory: De brûkersgids befettet in list fan alle brûkers dy¥t derfoar keazen hawwe om ûntdekt wurde te kinnen. - require_invite_text: Meitsje it ynfoljen fan "Wêrom wolle jo jo hjir registrearje?" ferplicht yn stee fan opsjoneel, wannear’t registraasjes hânmjittich goedkard wurde moatte + require_invite_text: Meitsje it ynfoljen fan ‘Wêrom wolle jo jo hjir registrearje?’ ferplicht yn stee fan opsjoneel, wannear’t registraasjes hânmjittich goedkard wurde moatte site_contact_email: Hoe minsken jo berikke kinne foar juridyske fragen of stipe. site_contact_username: Hoe minsken jo op Mastodon berikke kinne. site_extended_description: Alle oanfoljende ynformaasje dy’t nuttich wêze kin foar besikers en jo brûkers. Kin opmakke wurde mei Markdown. diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 51baaf7a4e..190c811dfd 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -40,7 +40,7 @@ zh-CN: current_email: 当前的电子邮箱 label: 更改电子邮箱 new_email: 新的电子邮箱 - submit: 更改电子邮件地址 + submit: 更改电子邮箱 title: 更改 %{username} 的电子邮箱 change_role: changed_msg: 已成功更改角色! @@ -68,8 +68,8 @@ zh-CN: enable_sign_in_token_auth: 启用电子邮件令牌认证 enabled: 已启用 enabled_msg: 成功解冻 %{username} 的账号 - followers: 关注者 - follows: 正在关注 + followers: 粉丝 + follows: 关注 header: 个人资料页横幅图片 inbox_url: 收件箱(Inbox)URL invite_request_text: 加入理由 diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index d5b713b347..97268eea6d 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -34,6 +34,40 @@ RSpec.describe ActivityPub::LinkedDataSignature do end end + context 'when local account record is missing a public key' do + let(:raw_signature) do + { + 'creator' => 'http://example.com/alice', + 'created' => '2017-09-23T20:21:34Z', + } + end + + let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) } + + let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) } + + before do + # Ensure signature is computed with the old key + signature + + # Unset key + old_key = sender.public_key + sender.update!(private_key: '', public_key: '') + + allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub) + + allow(service_stub).to receive(:call).with('http://example.com/alice', id: false) do + sender.update!(public_key: old_key) + sender + end + end + + it 'fetches key and returns creator' do + expect(subject.verify_actor!).to eq sender + expect(service_stub).to have_received(:call).with('http://example.com/alice', id: false).once + end + end + context 'when signature is missing' do let(:signature) { nil } diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb new file mode 100644 index 0000000000..a17c7532e9 --- /dev/null +++ b/spec/models/preview_card_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PreviewCard do + describe 'validations' do + describe 'urls' do + it 'allows http schemes' do + record = described_class.new(url: 'http://example.host/path') + + expect(record).to be_valid + end + + it 'allows https schemes' do + record = described_class.new(url: 'https://example.host/path') + + expect(record).to be_valid + end + + it 'does not allow javascript: schemes' do + record = described_class.new(url: 'javascript:alert()') + + expect(record).to_not be_valid + expect(record).to model_have_error_on_field(:url) + end + end + end +end diff --git a/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb new file mode 100644 index 0000000000..82a1dee6d7 --- /dev/null +++ b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'admin/trends/links/_preview_card.html.haml' do + it 'correctly escapes user supplied url values' do + form = instance_double(ActionView::Helpers::FormHelper, check_box: nil) + trend = PreviewCardTrend.new(allowed: false) + preview_card = Fabricate.build( + :preview_card, + url: 'https://host.example/path?query=