diff options
author | ThibG <thib@sitedethib.com> | 2020-05-13 23:46:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-13 23:46:09 +0200 |
commit | e1d282023432ff4a88cb795f3a9889704314e448 (patch) | |
tree | 89f9525142d0bf876a4fb6e0117163c0a76bb78a /app | |
parent | c6ff4c634caf718adf7280e04909c091d15add1d (diff) | |
parent | d147dd7588502f22ac825780bb1d6e54eb3613e6 (diff) |
Merge pull request #1327 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
Diffstat (limited to 'app')
61 files changed, 321 insertions, 193 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 52d09cff8..e3d8c1061 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -41,7 +41,7 @@ class AccountsController < ApplicationController format.rss do expires_in 1.minute, public: true - @statuses = filtered_statuses.without_reblogs.without_replies.limit(PAGE_SIZE) + @statuses = filtered_statuses.without_reblogs.limit(PAGE_SIZE) @statuses = cache_collection(@statuses, Status) render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag]) end @@ -130,11 +130,11 @@ class AccountsController < ApplicationController end def media_requested? - request.path.ends_with?('/media') && !tag_requested? + request.path.split('.').first.ends_with?('/media') && !tag_requested? end def replies_requested? - request.path.ends_with?('/with_replies') && !tag_requested? + request.path.split('.').first.ends_with?('/with_replies') && !tag_requested? end def tag_requested? diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 1daa1ed0d..2277067c9 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController return [] if hide_results? scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id scope.merge(paginated_follows).to_a end diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 6fc23cf75..93d4bd3a4 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -20,7 +20,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController return [] if hide_results? scope = default_accounts - scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? + scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id scope.merge(paginated_follows).to_a end diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index 581befef1..c6e7854d9 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController end def public_timeline_statuses - Status.as_public_timeline(current_account, truthy_param?(:local)) + Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local)) end def insert_pagination_headers @@ -47,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController end def pagination_params(core_params) - params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) + params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params) end def next_path diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index eac9dde6f..c36561b86 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -113,6 +113,13 @@ class Auth::SessionsController < Devise::SessionsController render :two_factor end + def require_no_authentication + super + # Delete flash message that isn't entirely useful and may be confusing in + # most cases because /web doesn't display/clear flash messages. + flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated') + end + private def set_pack diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index b43859d9d..d1384ed56 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -28,18 +28,6 @@ module Localized end def request_locale - preferred_locale || compatible_locale - end - - def preferred_locale - http_accept_language.preferred_language_from(available_locales) - end - - def compatible_locale - http_accept_language.compatible_language_from(available_locales) - end - - def available_locales - I18n.available_locales.reverse + http_accept_language.language_region_compatible_from(I18n.available_locales) end end diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb index e84c1aca6..b217b3c3b 100644 --- a/app/controllers/settings/identity_proofs_controller.rb +++ b/app/controllers/settings/identity_proofs_controller.rb @@ -22,8 +22,7 @@ class Settings::IdentityProofsController < Settings::BaseController if current_account.username.casecmp(params[:username]).zero? render layout: 'auth' else - flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username) - redirect_to settings_identity_proofs_path + redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username) end end @@ -35,11 +34,16 @@ class Settings::IdentityProofsController < Settings::BaseController PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof? redirect_to @proof.on_success_path(params[:user_agent]) else - flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize) - redirect_to settings_identity_proofs_path + redirect_to settings_identity_proofs_path, alert: I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize) end end + def destroy + @proof = current_account.identity_proofs.find(params[:id]) + @proof.destroy! + redirect_to settings_identity_proofs_path, success: I18n.t('identity_proofs.removed') + end + private def check_enabled diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index b66e827fe..4da68500a 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -7,13 +7,13 @@ module HomeHelper } end - def account_link_to(account, button = '', size: 36, path: nil) + def account_link_to(account, button = '', path: nil) content_tag(:div, class: 'account') do content_tag(:div, class: 'account__wrapper') do section = if account.nil? content_tag(:div, class: 'account__display-name') do content_tag(:div, class: 'account__avatar-wrapper') do - content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})") + image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'account__avatar') end + content_tag(:span, class: 'display-name') do content_tag(:strong, t('about.contact_missing')) + @@ -23,7 +23,7 @@ module HomeHelper else link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do content_tag(:div, class: 'account__avatar-wrapper') do - content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})") + image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar') end + content_tag(:span, class: 'display-name') do content_tag(:bdi) do diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 74544bad9..87718dc05 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -68,6 +68,7 @@ module SettingsHelper tr: 'Türkçe', uk: 'Українська', ur: 'اُردُو', + vi: 'Tiếng Việt', 'zh-CN': '简体中文', 'zh-HK': '繁體中文(香港)', 'zh-TW': '繁體中文(臺灣)', diff --git a/app/helpers/webfinger_helper.rb b/app/helpers/webfinger_helper.rb new file mode 100644 index 000000000..70c493210 --- /dev/null +++ b/app/helpers/webfinger_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module WebfingerHelper + def webfinger!(uri) + hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri) + + raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri + + opts = { + ssl: !hidden_service_uri, + + headers: { + 'User-Agent': Mastodon::Version.user_agent, + }, + } + + Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger + end +end diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index 2f82ea805..875013efc 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => { export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); -export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); +export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`); export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept); export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 50e36531e..b01109134 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -121,7 +121,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { }; export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); -export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done); +export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index fb0f165ff..c7b54649c 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -186,10 +186,12 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); } else { if (account.getIn(['relationship', 'following'])) { - if (account.getIn(['relationship', 'showing_reblogs'])) { - menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); - } else { - menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + if (!account.getIn(['relationship', 'muting'])) { + if (account.getIn(['relationship', 'showing_reblogs'])) { + menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + } else { + menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + } } menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.js b/app/javascript/flavours/glitch/features/compose/components/poll_form.js index 57fac10ac..e4b5104f3 100644 --- a/app/javascript/flavours/glitch/features/compose/components/poll_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.js @@ -28,6 +28,7 @@ class Option extends React.PureComponent { title: PropTypes.string.isRequired, index: PropTypes.number.isRequired, isPollMultiple: PropTypes.bool, + autoFocus: PropTypes.bool, onChange: PropTypes.func.isRequired, onRemove: PropTypes.func.isRequired, suggestions: ImmutablePropTypes.list, @@ -58,7 +59,7 @@ class Option extends React.PureComponent { } render () { - const { isPollMultiple, title, index, intl } = this.props; + const { isPollMultiple, title, index, autoFocus, intl } = this.props; return ( <li> @@ -75,6 +76,7 @@ class Option extends React.PureComponent { onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionSelected={this.onSuggestionSelected} searchTokens={[':']} + autoFocus={autoFocus} /> </label> @@ -125,10 +127,12 @@ class PollForm extends ImmutablePureComponent { return null; } + const autoFocusIndex = options.indexOf(''); + return ( <div className='compose-form__poll-wrapper'> <ul> - {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} {...other} />)} + {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} autoFocus={i === autoFocusIndex} {...other} />)} {options.size < pollLimits.max_options && ( <label className='poll__text editable'> <span className={classNames('poll__input')} style={{ opacity: 0 }} /> diff --git a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js new file mode 100644 index 000000000..756b6fe06 --- /dev/null +++ b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import SettingToggle from '../../notifications/components/setting_toggle'; + +export default @injectIntl +class ColumnSettings extends React.PureComponent { + + static propTypes = { + settings: ImmutablePropTypes.map.isRequired, + onChange: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + columnId: PropTypes.string, + }; + + render () { + const { settings, onChange } = this.props; + + return ( + <div> + <div className='column-settings__row'> + <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} /> + <SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} /> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js index ec4d74737..5091bfb90 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import ColumnSettings from 'flavours/glitch/features/community_timeline/components/column_settings'; +import ColumnSettings from '../components/column_settings'; import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeColumnParams } from 'flavours/glitch/actions/columns'; diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js index 4d139a326..3f720b885 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.js +++ b/app/javascript/flavours/glitch/features/public_timeline/index.js @@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => { const columns = state.getIn(['settings', 'columns']); const index = columns.findIndex(c => c.get('uuid') === uuid); const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']); + const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]); return { hasUnread: !!timelineState && timelineState.get('unread') > 0, onlyMedia, + onlyRemote, }; }; @@ -46,15 +48,16 @@ class PublicTimeline extends React.PureComponent { multiColumn: PropTypes.bool, hasUnread: PropTypes.bool, onlyMedia: PropTypes.bool, + onlyRemote: PropTypes.bool, }; handlePin = () => { - const { columnId, dispatch, onlyMedia } = this.props; + const { columnId, dispatch, onlyMedia, onlyRemote } = this.props; if (columnId) { dispatch(removeColumn(columnId)); } else { - dispatch(addColumn('PUBLIC', { other: { onlyMedia } })); + dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } })); } } @@ -68,19 +71,19 @@ class PublicTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch, onlyMedia } = this.props; + const { dispatch, onlyMedia, onlyRemote } = this.props; - dispatch(expandPublicTimeline({ onlyMedia })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia })); + dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); } componentDidUpdate (prevProps) { - if (prevProps.onlyMedia !== this.props.onlyMedia) { - const { dispatch, onlyMedia } = this.props; + if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { + const { dispatch, onlyMedia, onlyRemote } = this.props; this.disconnect(); - dispatch(expandPublicTimeline({ onlyMedia })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia })); + dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); } } @@ -96,13 +99,13 @@ class PublicTimeline extends React.PureComponent { } handleLoadMore = maxId => { - const { dispatch, onlyMedia } = this.props; + const { dispatch, onlyMedia, onlyRemote } = this.props; - dispatch(expandPublicTimeline({ maxId, onlyMedia })); + dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote })); } render () { - const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; + const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; const pinned = !!columnId; return ( @@ -121,7 +124,7 @@ class PublicTimeline extends React.PureComponent { </ColumnHeader> <StatusListContainer - timelineId={`public${onlyMedia ? ':media' : ''}`} + timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 431909c72..2de24bea5 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -37,6 +37,7 @@ const componentMap = { 'HOME': HomeTimeline, 'NOTIFICATIONS': Notifications, 'PUBLIC': PublicTimeline, + 'REMOTE': PublicTimeline, 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, 'DIRECT': DirectTimeline, diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index f0a44aa94..ac5f3ebb0 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -545,13 +545,6 @@ $small-breakpoint: 960px; flex: 0 0 auto; } - &__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - @include avatar-size(44px); - } - .display-name { font-size: 15px; @@ -752,12 +745,6 @@ $small-breakpoint: 960px; display: flex; align-items: center; } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } } &__counters__wrapper { diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index ccd620215..610e48f92 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -38,9 +38,14 @@ .account__avatar { @include avatar-radius(); + display: block; position: relative; cursor: pointer; + width: 36px; + height: 36px; + background-size: 36px 36px; + &-inline { display: inline-block; vertical-align: middle; diff --git a/app/javascript/flavours/glitch/styles/statuses.scss b/app/javascript/flavours/glitch/styles/statuses.scss index 4122e121a..6fcc11e29 100644 --- a/app/javascript/flavours/glitch/styles/statuses.scss +++ b/app/javascript/flavours/glitch/styles/statuses.scss @@ -145,6 +145,11 @@ &__avatar { left: 15px; top: 17px; + + .account__avatar { + width: 48px; + height: 48px; + } } &__content { diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss index a6f7fc0be..531425573 100644 --- a/app/javascript/flavours/glitch/styles/widgets.scss +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -93,12 +93,6 @@ display: flex; align-items: center; } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } } .trends__item { diff --git a/app/javascript/images/logo_transparent_white.svg b/app/javascript/images/logo_transparent_white.svg new file mode 100644 index 000000000..f061ffe4c --- /dev/null +++ b/app/javascript/images/logo_transparent_white.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#fff"/></svg> diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 79b08bdda..080d665f4 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => { export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); -export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); +export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`); export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept); export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 861827d33..01f0fb015 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { }; export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); -export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done); +export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 92780a70b..8c85bbc39 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -192,10 +192,12 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); } else { if (account.getIn(['relationship', 'following'])) { - if (account.getIn(['relationship', 'showing_reblogs'])) { - menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); - } else { - menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + if (!account.getIn(['relationship', 'muting'])) { + if (account.getIn(['relationship', 'showing_reblogs'])) { + menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + } else { + menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); + } } menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js index 271019dfe..88894ae59 100644 --- a/app/javascript/mastodon/features/compose/components/poll_form.js +++ b/app/javascript/mastodon/features/compose/components/poll_form.js @@ -27,6 +27,7 @@ class Option extends React.PureComponent { title: PropTypes.string.isRequired, index: PropTypes.number.isRequired, isPollMultiple: PropTypes.bool, + autoFocus: PropTypes.bool, onChange: PropTypes.func.isRequired, onRemove: PropTypes.func.isRequired, onToggleMultiple: PropTypes.func.isRequired, @@ -71,7 +72,7 @@ class Option extends React.PureComponent { } render () { - const { isPollMultiple, title, index, intl } = this.props; + const { isPollMultiple, title, index, autoFocus, intl } = this.props; return ( <li> @@ -96,6 +97,7 @@ class Option extends React.PureComponent { onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionSelected={this.onSuggestionSelected} searchTokens={[':']} + autoFocus={autoFocus} /> </label> @@ -146,10 +148,12 @@ class PollForm extends ImmutablePureComponent { return null; } + const autoFocusIndex = options.indexOf(''); + return ( <div className='compose-form__poll-wrapper'> <ul> - {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} {...other} />)} + {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)} </ul> <div className='poll__footer'> diff --git a/app/javascript/mastodon/features/public_timeline/components/column_settings.js b/app/javascript/mastodon/features/public_timeline/components/column_settings.js new file mode 100644 index 000000000..756b6fe06 --- /dev/null +++ b/app/javascript/mastodon/features/public_timeline/components/column_settings.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import SettingToggle from '../../notifications/components/setting_toggle'; + +export default @injectIntl +class ColumnSettings extends React.PureComponent { + + static propTypes = { + settings: ImmutablePropTypes.map.isRequired, + onChange: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + columnId: PropTypes.string, + }; + + render () { + const { settings, onChange } = this.props; + + return ( + <div> + <div className='column-settings__row'> + <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} /> + <SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} /> + </div> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js index c56caa59e..8c9e8aef4 100644 --- a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import ColumnSettings from '../../community_timeline/components/column_settings'; +import ColumnSettings from '../components/column_settings'; import { changeSetting } from '../../../actions/settings'; import { changeColumnParams } from '../../../actions/columns'; diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 7aabd7f6e..988b1b070 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => { const columns = state.getIn(['settings', 'columns']); const index = columns.findIndex(c => c.get('uuid') === uuid); const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']); + const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]); return { hasUnread: !!timelineState && timelineState.get('unread') > 0, onlyMedia, + onlyRemote, }; }; @@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent { multiColumn: PropTypes.bool, hasUnread: PropTypes.bool, onlyMedia: PropTypes.bool, + onlyRemote: PropTypes.bool, }; handlePin = () => { - const { columnId, dispatch, onlyMedia } = this.props; + const { columnId, dispatch, onlyMedia, onlyRemote } = this.props; if (columnId) { dispatch(removeColumn(columnId)); } else { - dispatch(addColumn('PUBLIC', { other: { onlyMedia } })); + dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } })); } } @@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent { } componentDidMount () { - const { dispatch, onlyMedia } = this.props; + const { dispatch, onlyMedia, onlyRemote } = this.props; - dispatch(expandPublicTimeline({ onlyMedia })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia })); + dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); } componentDidUpdate (prevProps) { - if (prevProps.onlyMedia !== this.props.onlyMedia) { - const { dispatch, onlyMedia } = this.props; + if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { + const { dispatch, onlyMedia, onlyRemote } = this.props; this.disconnect(); - dispatch(expandPublicTimeline({ onlyMedia })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia })); + dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); } } @@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent { } handleLoadMore = maxId => { - const { dispatch, onlyMedia } = this.props; + const { dispatch, onlyMedia, onlyRemote } = this.props; - dispatch(expandPublicTimeline({ maxId, onlyMedia })); + dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote })); } render () { - const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props; + const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; const pinned = !!columnId; return ( @@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent { </ColumnHeader> <StatusListContainer - timelineId={`public${onlyMedia ? ':media' : ''}`} + timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} diff --git a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js index 89cb2458d..d2791ce08 100644 --- a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js +++ b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js @@ -5,30 +5,21 @@ import ColumnHeader from '../column_header'; describe('<Column />', () => { describe('<ColumnHeader /> click handler', () => { - const originalRaf = global.requestAnimationFrame; - - beforeEach(() => { - global.requestAnimationFrame = jest.fn(); - }); - - afterAll(() => { - global.requestAnimationFrame = originalRaf; - }); - it('runs the scroll animation if the column contains scrollable content', () => { const wrapper = mount( <Column heading='notifications'> <div className='scrollable' /> </Column>, ); + const scrollToMock = jest.fn(); + wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock; wrapper.find(ColumnHeader).find('button').simulate('click'); - expect(global.requestAnimationFrame.mock.calls.length).toEqual(1); + expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 }); }); it('does not try to scroll if there is no scrollable content', () => { const wrapper = mount(<Column heading='notifications' />); wrapper.find(ColumnHeader).find('button').simulate('click'); - expect(global.requestAnimationFrame.mock.calls.length).toEqual(0); }); }); }); diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 8bc4bfc0e..9b03cf26d 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -37,6 +37,7 @@ const componentMap = { 'HOME': HomeTimeline, 'NOTIFICATIONS': Notifications, 'PUBLIC': PublicTimeline, + 'REMOTE': PublicTimeline, 'COMMUNITY': CommunityTimeline, 'HASHTAG': HashtagTimeline, 'DIRECT': DirectTimeline, diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 711f34965..3be0aee49 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -543,12 +543,6 @@ $small-breakpoint: 960px; flex: 0 0 auto; } - &__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } - .display-name { font-size: 15px; @@ -749,12 +743,6 @@ $small-breakpoint: 960px; display: flex; align-items: center; } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } } &__counters__wrapper { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 6c33b709d..65a3bab8d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1318,8 +1318,13 @@ .account__avatar { @include avatar-radius; + display: block; position: relative; + width: 36px; + height: 36px; + background-size: 36px 36px; + &-inline { display: inline-block; vertical-align: middle; diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss index 0b7be7afd..a8fd2936c 100644 --- a/app/javascript/styles/mastodon/statuses.scss +++ b/app/javascript/styles/mastodon/statuses.scss @@ -149,6 +149,11 @@ &__avatar { left: 15px; top: 17px; + + .account__avatar { + width: 48px; + height: 48px; + } } &__content { diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index ca050a8d9..5b97d1ec4 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -93,12 +93,6 @@ display: flex; align-items: center; } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } } .trends__item { diff --git a/app/lib/proof_provider/keybase/config_serializer.rb b/app/lib/proof_provider/keybase/config_serializer.rb index 2840f1823..fbce7aeee 100644 --- a/app/lib/proof_provider/keybase/config_serializer.rb +++ b/app/lib/proof_provider/keybase/config_serializer.rb @@ -22,7 +22,12 @@ class ProofProvider::Keybase::ConfigSerializer < ActiveModel::Serializer end def logo - { svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')), svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')) } + { + svg_black: full_asset_url(asset_pack_path('media/images/logo_transparent_black.svg')), + svg_white: full_asset_url(asset_pack_path('media/images/logo_transparent_white.svg')), + svg_full: full_asset_url(asset_pack_path('media/images/logo.svg')), + svg_full_darkmode: full_asset_url(asset_pack_path('media/images/logo.svg')), + } end def brand_color diff --git a/app/lib/request.rb b/app/lib/request.rb index c476e7785..247c32958 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -73,8 +73,6 @@ class Request response.body_with_limit if http_client.persistent? yield response if block_given? - rescue => e - raise e.class, e.message, e.backtrace[0] ensure http_client.close unless http_client.persistent? end diff --git a/app/lib/rss/serializer.rb b/app/lib/rss/serializer.rb new file mode 100644 index 000000000..fd56c568c --- /dev/null +++ b/app/lib/rss/serializer.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class RSS::Serializer + private + + def render_statuses(builder, statuses) + statuses.each do |status| + builder.item do |item| + item.title(status_title(status)) + .link(ActivityPub::TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size) + end + end + end + end + + def status_title(status) + return "#{status.account.acct} deleted status" if status.destroyed? + + preview = status.proper.spoiler_text.presence || status.proper.text + if preview.length > 30 || preview[0, 30].include?("\n") + preview = preview[0, 30] + preview = preview[0, preview.index("\n").presence || 30] + '…' + end + + preview = "#{status.proper.spoiler_text.present? ? 'CW ' : ''}“#{preview}”#{status.proper.sensitive? ? ' (sensitive)' : ''}" + + if status.reblog? + "#{status.account.acct} boosted #{status.reblog.account.acct}: #{preview}" + else + "#{status.account.acct}: #{preview}" + end + end +end diff --git a/app/lib/sidekiq_error_handler.rb b/app/lib/sidekiq_error_handler.rb index 8eb6b942d..b07817d45 100644 --- a/app/lib/sidekiq_error_handler.rb +++ b/app/lib/sidekiq_error_handler.rb @@ -1,13 +1,24 @@ # frozen_string_literal: true class SidekiqErrorHandler + BACKTRACE_LIMIT = 3 + def call(*) yield rescue Mastodon::HostValidationError # Do not retry + rescue => e + limit_backtrace_and_raise(e) ensure socket = Thread.current[:statsd_socket] socket&.close Thread.current[:statsd_socket] = nil end + + private + + def limit_backtrace_and_raise(e) + e.set_backtrace(e.backtrace.first(BACKTRACE_LIMIT)) + raise e + end end diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb index e6859bf3d..9135ff144 100644 --- a/app/models/relationship_filter.rb +++ b/app/models/relationship_filter.rb @@ -23,7 +23,7 @@ class RelationshipFilter scope = scope_for('relationship', params['relationship'].to_s.strip) params.each do |key, value| - next if key.to_s == 'page' + next if %w(relationship page).include?(key) scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present? end diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb index 5ea535287..30b84f7d5 100644 --- a/app/models/remote_follow.rb +++ b/app/models/remote_follow.rb @@ -3,6 +3,7 @@ class RemoteFollow include ActiveModel::Validations include RoutingHelper + include WebfingerHelper attr_accessor :acct, :addressable_template @@ -71,7 +72,7 @@ class RemoteFollow end def acct_resource - @acct_resource ||= Goldfinger.finger("acct:#{acct}") + @acct_resource ||= webfinger!("acct:#{acct}") rescue Goldfinger::Error, HTTP::ConnectionError nil end diff --git a/app/models/status.rb b/app/models/status.rb index 34fa00912..341f72090 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -203,14 +203,6 @@ class Status < ApplicationRecord preview_cards.first end - def title - if destroyed? - "#{account.acct} deleted status" - else - reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}" - end - end - def hidden? !distributable? end @@ -342,7 +334,7 @@ class Status < ApplicationRecord query = timeline_scope(local_only) query = query.without_replies unless Setting.show_replies_in_public_timelines - apply_timeline_filters(query, account, local_only) + apply_timeline_filters(query, account, [:local, true].include?(local_only)) end def as_tag_timeline(tag, account = nil, local_only = false) @@ -434,8 +426,15 @@ class Status < ApplicationRecord private - def timeline_scope(local_only = false) - starting_scope = local_only ? Status.local : Status + def timeline_scope(scope = false) + starting_scope = case scope + when :local, true + Status.local + when :remote + Status.remote + else + Status + end starting_scope = starting_scope.with_public_visibility if Setting.show_reblogs_in_public_timelines starting_scope diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index c5dbb58ba..c407a7789 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -94,11 +94,11 @@ class Web::PushSubscription < ApplicationRecord def find_or_create_access_token Doorkeeper::AccessToken.find_or_create_for( - Doorkeeper::Application.find_by(superapp: true), - session_activation.user_id, - Doorkeeper::OAuth::Scopes.from_string('read write follow push'), - Doorkeeper.configuration.access_token_expires_in, - Doorkeeper.configuration.refresh_token_enabled? + application: Doorkeeper::Application.find_by(superapp: true), + resource_owner: session_activation.user_id, + scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'), + expires_in: Doorkeeper.configuration.access_token_expires_in, + use_refresh_token: Doorkeeper.configuration.refresh_token_enabled? ) end end diff --git a/app/serializers/oembed_serializer.rb b/app/serializers/oembed_serializer.rb index 01689633b..d6261d724 100644 --- a/app/serializers/oembed_serializer.rb +++ b/app/serializers/oembed_serializer.rb @@ -4,7 +4,7 @@ class OEmbedSerializer < ActiveModel::Serializer include RoutingHelper include ActionView::Helpers::TagHelper - attributes :type, :version, :title, :author_name, + attributes :type, :version, :author_name, :author_url, :provider_name, :provider_url, :cache_age, :html, :width, :height diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb index ee972ff96..81e24af0d 100644 --- a/app/serializers/rss/account_serializer.rb +++ b/app/serializers/rss/account_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class RSS::AccountSerializer +class RSS::AccountSerializer < RSS::Serializer include ActionView::Helpers::NumberHelper include AccountsHelper include RoutingHelper @@ -17,18 +17,7 @@ class RSS::AccountSerializer builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar? builder.cover(full_asset_url(account.header.url(:original))) if account.header? - statuses.each do |status| - builder.item do |item| - item.title(status.title) - .link(ActivityPub::TagManager.instance.url_for(status)) - .pub_date(status.created_at) - .description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str) - - status.media_attachments.each do |media| - item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size) - end - end - end + render_statuses(builder, statuses) builder.to_xml end diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb index ea26189a2..e549ac367 100644 --- a/app/serializers/rss/tag_serializer.rb +++ b/app/serializers/rss/tag_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class RSS::TagSerializer +class RSS::TagSerializer < RSS::Serializer include ActionView::Helpers::NumberHelper include ActionView::Helpers::SanitizeHelper include RoutingHelper @@ -14,18 +14,7 @@ class RSS::TagSerializer .logo(full_pack_url('media/images/logo.svg')) .accent_color('2b90d9') - statuses.each do |status| - builder.item do |item| - item.title(status.title) - .link(ActivityPub::TagManager.instance.url_for(status)) - .pub_date(status.created_at) - .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) - - status.media_attachments.each do |media| - item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size) - end - end - end + render_statuses(builder, statuses) builder.to_xml end diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb index d65c8f951..83fbf6d07 100644 --- a/app/services/activitypub/fetch_remote_account_service.rb +++ b/app/services/activitypub/fetch_remote_account_service.rb @@ -3,6 +3,7 @@ class ActivityPub::FetchRemoteAccountService < BaseService include JsonLdHelper include DomainControlHelper + include WebfingerHelper SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze @@ -35,12 +36,12 @@ class ActivityPub::FetchRemoteAccountService < BaseService private def verified_webfinger? - webfinger = Goldfinger.finger("acct:#{@username}@#{@domain}") + webfinger = webfinger!("acct:#{@username}@#{@domain}") confirmed_username, confirmed_domain = split_acct(webfinger.subject) return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero? - webfinger = Goldfinger.finger("acct:#{confirmed_username}@#{confirmed_domain}") + webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}") @username, @domain = split_acct(webfinger.subject) self_reference = webfinger.link('self') diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 31237337a..707672ee0 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -73,11 +73,18 @@ class BatchedRemoveStatusService < BaseService redis.pipelined do redis.publish('timeline:public', payload) - redis.publish('timeline:public:local', payload) if status.local? - + if status.local? + redis.publish('timeline:public:local', payload) + else + redis.publish('timeline:public:remote', payload) + end if status.media_attachments.any? redis.publish('timeline:public:media', payload) - redis.publish('timeline:public:local:media', payload) if status.local? + if status.local? + redis.publish('timeline:public:local:media', payload) + else + redis.publish('timeline:public:remote:media', payload) + end end @tags[status.id].each do |hashtag| diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 72f716dc5..dd9c1264d 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -86,14 +86,22 @@ class FanOutOnWriteService < BaseService Rails.logger.debug "Delivering status #{status.id} to public timeline" Redis.current.publish('timeline:public', @payload) - Redis.current.publish('timeline:public:local', @payload) if status.local? + if status.local? + Redis.current.publish('timeline:public:local', @payload) + else + Redis.current.publish('timeline:public:remote', @payload) + end end def deliver_to_media(status) Rails.logger.debug "Delivering status #{status.id} to media timeline" Redis.current.publish('timeline:public:media', @payload) - Redis.current.publish('timeline:public:local:media', @payload) if status.local? + if status.local? + Redis.current.publish('timeline:public:local:media', @payload) + else + Redis.current.publish('timeline:public:remote:media', @payload) + end end def deliver_to_direct_timelines(status) diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 1ddce675c..a5aafee21 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -142,14 +142,22 @@ class RemoveStatusService < BaseService return unless @status.public_visibility? redis.publish('timeline:public', @payload) - redis.publish('timeline:public:local', @payload) if @status.local? + if @status.local? + redis.publish('timeline:public:local', @payload) + else + redis.publish('timeline:public:remote', @payload) + end end def remove_from_media return unless @status.public_visibility? redis.publish('timeline:public:media', @payload) - redis.publish('timeline:public:local:media', @payload) if @status.local? + if @status.local? + redis.publish('timeline:public:local:media', @payload) + else + redis.publish('timeline:public:remote:media', @payload) + end end def remove_from_direct diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 1ad9ed407..17ace100c 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -3,6 +3,7 @@ class ResolveAccountService < BaseService include JsonLdHelper include DomainControlHelper + include WebfingerHelper class WebfingerRedirectError < StandardError; end @@ -76,7 +77,7 @@ class ResolveAccountService < BaseService end def process_webfinger!(uri, redirected = false) - @webfinger = Goldfinger.finger("acct:#{uri}") + @webfinger = webfinger!("acct:#{uri}") confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@') if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index 07e06100a..565c4ed59 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -27,7 +27,7 @@ .avatar-stack - @instance_presenter.sample_accounts.each do |account| - = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar' + = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, alt: '', class: 'account__avatar' - if Setting.timeline_preview .directory__tag diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 0263b80fb..2149fcc46 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -25,7 +25,7 @@ - target_account = reports.first.target_account .report-card .report-card__profile - = account_link_to target_account, '', size: 36, path: admin_account_path(target_account.id) + = account_link_to target_account, '', path: admin_account_path(target_account.id) .report-card__profile__stats = link_to t('admin.reports.account.notes', count: target_account.targeted_moderation_notes.count), admin_account_path(target_account.id) %br/ diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index 1170332ff..febfb7d17 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -25,7 +25,7 @@ .directory__card__bar = link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do .avatar - = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' + = image_tag account.avatar.url, alt: '', class: 'u-photo' .display-name %bdi diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 99ab3729e..92edaea3c 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -28,6 +28,8 @@ = javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous' = csrf_meta_tags + = stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style' + = yield :header_tags -# These must come after :header_tags to ensure our initial state has been defined. diff --git a/app/views/settings/identity_proofs/_proof.html.haml b/app/views/settings/identity_proofs/_proof.html.haml index 524827ad7..14e8e91be 100644 --- a/app/views/settings/identity_proofs/_proof.html.haml +++ b/app/views/settings/identity_proofs/_proof.html.haml @@ -18,3 +18,4 @@ %td = table_link_to 'external-link', t('identity_proofs.view_proof'), proof.badge.proof_url if proof.badge.proof_url + = table_link_to 'trash', t('identity_proofs.remove'), settings_identity_proof_path(proof), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 544b92330..33b81c748 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -3,9 +3,9 @@ = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do .detailed-status__display-avatar - if current_account&.user&.setting_auto_play_gif || autoplay - = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo' + = image_tag status.account.avatar_original_url, alt: '', class: 'account__avatar u-photo' - else - = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo' + = image_tag status.account.avatar_static_url, alt: '', class: 'account__avatar u-photo' %span.display-name %bdi %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay) diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index f959056cd..b7a2b7116 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -9,9 +9,9 @@ .status__avatar %div - if current_account&.user&.setting_auto_play_gif || autoplay - = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar' + = image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar' - else - = image_tag status.account.avatar_static_url, width: 48, height: 48, alt: '', class: 'u-photo account__avatar' + = image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar' %span.display-name %bdi %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay) diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 14e37dc34..60775787a 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -52,13 +52,9 @@ class ActivityPub::DeliveryWorker end end - begin - light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD) - .with_cool_off_time(STOPLIGHT_COOLDOWN) - .run - rescue Stoplight::Error::RedLight => e - raise e.class, e.message, e.backtrace.first(3) - end + light.with_threshold(STOPLIGHT_FAILURE_THRESHOLD) + .with_cool_off_time(STOPLIGHT_COOLDOWN) + .run end def failure_tracker diff --git a/app/workers/redownload_media_worker.rb b/app/workers/redownload_media_worker.rb index 98e995918..071501a49 100644 --- a/app/workers/redownload_media_worker.rb +++ b/app/workers/redownload_media_worker.rb @@ -11,7 +11,7 @@ class RedownloadMediaWorker return if media_attachment.remote_url.blank? - media_attachment.reset_file! + media_attachment.file_remote_url = media_attachment.remote_url media_attachment.save rescue ActiveRecord::RecordNotFound true |