diff options
Diffstat (limited to 'app')
20 files changed, 147 insertions, 48 deletions
diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index e469c7d21..8b63d0490 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -6,6 +6,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController respond_to :json def show - render json: doorkeeper_token.application, serializer: REST::StatusSerializer::ApplicationSerializer + render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) end end diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 7fb84bd1e..1d3130604 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -83,7 +83,11 @@ export default class StatusActionBar extends ImmutablePureComponent { ] handleReplyClick = () => { - this.props.onReply(this.props.status, this.context.router.history); + if (me) { + this.props.onReply(this.props.status, this.context.router.history); + } else { + this._openInteractionDialog('reply'); + } } handleShareClick = () => { @@ -94,17 +98,29 @@ export default class StatusActionBar extends ImmutablePureComponent { } handleFavouriteClick = (e) => { - this.props.onFavourite(this.props.status, e); + if (me) { + this.props.onFavourite(this.props.status, e); + } else { + this._openInteractionDialog('favourite'); + } } handleBookmarkClick = (e) => { this.props.onBookmark(this.props.status, e); } - handleReblogClick = (e) => { - this.props.onReblog(this.props.status, e); + handleReblogClick = e => { + if (me) { + this.props.onReblog(this.props.status, e); + } else { + this._openInteractionDialog('reblog'); + } } + _openInteractionDialog = type => { + window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); + } + handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); } @@ -174,7 +190,7 @@ export default class StatusActionBar extends ImmutablePureComponent { const mutingConversation = status.get('muted'); const anonymousAccess = !me; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); - const reblogDisabled = anonymousAccess || (status.get('visibility') === 'direct' || (status.get('visibility') === 'private' && me !== status.getIn(['account', 'id']))); + const reblogDisabled = status.get('visibility') === 'direct' || (status.get('visibility') === 'private' && me !== status.getIn(['account', 'id'])); const reblogMessage = status.get('visibility') === 'private' ? messages.reblog_private : messages.reblog; let menu = []; @@ -243,7 +259,6 @@ export default class StatusActionBar extends ImmutablePureComponent { let replyButton = ( <IconButton className='status__action-bar-button' - disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} @@ -262,7 +277,7 @@ export default class StatusActionBar extends ImmutablePureComponent { <div className='status__action-bar'> {replyButton} <IconButton className='status__action-bar-button' disabled={reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblogMessage)} icon={reblogIcon} onClick={this.handleReblogClick} /> - <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> + <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> {shareButton} <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 8f49a9a30..120ae6817 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -98,7 +98,7 @@ export default class DetailedStatus extends ImmutablePureComponent { } render () { - const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; + const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; const { expanded, onToggleHidden, settings } = this.props; const outerStyle = { boxSizing: 'border-box' }; const { compact } = this.props; diff --git a/app/javascript/flavours/glitch/packs/error.js b/app/javascript/flavours/glitch/packs/error.js new file mode 100644 index 000000000..81c86c3ab --- /dev/null +++ b/app/javascript/flavours/glitch/packs/error.js @@ -0,0 +1,13 @@ +import ready from 'flavours/glitch/util/ready'; + +ready(() => { + const image = document.querySelector('img'); + + image.addEventListener('mouseenter', () => { + image.src = '/oops.gif'; + }); + + image.addEventListener('mouseleave', () => { + image.src = '/oops.png'; + }); +}); diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index 0c8342c44..d8f313381 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -7,7 +7,7 @@ pack: filename: packs/common.js stylesheet: true embed: packs/public.js - error: + error: packs/error.js home: filename: packs/home.js preload: diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index bd9fb1dab..a215b2625 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -7,7 +7,7 @@ pack: filename: common.js stylesheet: true embed: public.js - error: + error: error.js home: filename: application.js preload: diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 16c7caf1c..53d17d418 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -78,7 +78,11 @@ class StatusActionBar extends ImmutablePureComponent { ] handleReplyClick = () => { - this.props.onReply(this.props.status, this.context.router.history); + if (me) { + this.props.onReply(this.props.status, this.context.router.history); + } else { + this._openInteractionDialog('reply'); + } } handleShareClick = () => { @@ -91,11 +95,23 @@ class StatusActionBar extends ImmutablePureComponent { } handleFavouriteClick = () => { - this.props.onFavourite(this.props.status); + if (me) { + this.props.onFavourite(this.props.status); + } else { + this._openInteractionDialog('favourite'); + } + } + + handleReblogClick = e => { + if (me) { + this.props.onReblog(this.props.status, e); + } else { + this._openInteractionDialog('reblog'); + } } - handleReblogClick = (e) => { - this.props.onReblog(this.props.status, e); + _openInteractionDialog = type => { + window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); } handleDeleteClick = () => { @@ -233,9 +249,9 @@ class StatusActionBar extends ImmutablePureComponent { return ( <div className='status__action-bar'> - <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div> - <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /> - <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> + <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div> + <IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /> + <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> {shareButton} <div className='status__action-bar-dropdown'> diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index ca7ce6f8e..96a219c94 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'blocks', 'items']), + hasMore: !!state.getIn(['user_lists', 'blocks', 'next']), }); export default @connect(mapStateToProps) @@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -41,7 +43,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, accountIds, shouldUpdateScroll } = this.props; + const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props; if (!accountIds) { return ( @@ -59,6 +61,7 @@ class Blocks extends ImmutablePureComponent { <ScrollableList scrollKey='blocks' onLoadMore={this.handleLoadMore} + hasMore={hasMore} shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} > diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js index 5c1bd1161..7c075f5a5 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -19,6 +19,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ domains: state.getIn(['domain_lists', 'blocks', 'items']), + hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']), }); export default @connect(mapStateToProps) @@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, + hasMore: PropTypes.bool, domains: ImmutablePropTypes.orderedSet, intl: PropTypes.object.isRequired, }; @@ -42,7 +44,7 @@ class Blocks extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, domains, shouldUpdateScroll } = this.props; + const { intl, domains, shouldUpdateScroll, hasMore } = this.props; if (!domains) { return ( @@ -60,6 +62,7 @@ class Blocks extends ImmutablePureComponent { <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} + hasMore={hasMore} shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} > diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 56ae8764b..3871e0e5d 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'follow_requests', 'items']), + hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']), }); export default @connect(mapStateToProps) @@ -28,6 +29,7 @@ class FollowRequests extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, + hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -41,7 +43,7 @@ class FollowRequests extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, shouldUpdateScroll, accountIds } = this.props; + const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props; if (!accountIds) { return ( @@ -59,6 +61,7 @@ class FollowRequests extends ImmutablePureComponent { <ScrollableList scrollKey='follow_requests' onLoadMore={this.handleLoadMore} + hasMore={hasMore} shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} > diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index f979ef72f..4ed29a1ce 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -18,6 +18,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'mutes', 'items']), + hasMore: !!state.getIn(['user_lists', 'mutes', 'next']), }); export default @connect(mapStateToProps) @@ -28,6 +29,7 @@ class Mutes extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, shouldUpdateScroll: PropTypes.func, + hasMore: PropTypes.bool, accountIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, }; @@ -41,7 +43,7 @@ class Mutes extends ImmutablePureComponent { }, 300, { leading: true }); render () { - const { intl, shouldUpdateScroll, accountIds } = this.props; + const { intl, shouldUpdateScroll, hasMore, accountIds } = this.props; if (!accountIds) { return ( @@ -59,6 +61,7 @@ class Mutes extends ImmutablePureComponent { <ScrollableList scrollKey='mutes' onLoadMore={this.handleLoadMore} + hasMore={hasMore} shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} > diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 734353c9b..49bc43a7b 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -87,7 +87,7 @@ export default class DetailedStatus extends ImmutablePureComponent { } render () { - const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; + const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; const outerStyle = { boxSizing: 'border-box' }; const { compact } = this.props; diff --git a/app/javascript/packs/error.js b/app/javascript/packs/error.js new file mode 100644 index 000000000..685c89065 --- /dev/null +++ b/app/javascript/packs/error.js @@ -0,0 +1,13 @@ +import ready from '../mastodon/ready'; + +ready(() => { + const image = document.querySelector('img'); + + image.addEventListener('mouseenter', () => { + image.src = '/oops.gif'; + }); + + image.addEventListener('mouseleave', () => { + image.src = '/oops.png'; + }); +}); diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 746def625..4411ca0b4 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -100,12 +100,14 @@ body { vertical-align: middle; margin: 20px; - img { - display: block; - max-width: 470px; - width: 100%; - height: auto; - margin-top: -120px; + &__illustration { + img { + display: block; + max-width: 470px; + width: 100%; + height: auto; + margin-top: -120px; + } } h1 { diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 7e4e19531..8265810a0 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -138,11 +138,13 @@ class ActivityPub::Activity def status_from_object # If the status is already known, return it status = status_from_uri(object_uri) + return status unless status.nil? # If the boosted toot is embedded and it is a self-boost, handle it like a Create unless unsupported_object_type? actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri + if actor_id == @account.uri return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform end @@ -166,4 +168,21 @@ class ActivityPub::Activity ensure redis.del(key) end + + def fetch? + !@options[:delivery] + end + + def followed_by_local_accounts? + @account.passive_relationships.exists? + end + + def requested_through_relay? + @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? + end + + def reject_payload! + Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}") + nil + end end diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 04afeea20..9f8ffd9fb 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -2,8 +2,11 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def perform + return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity? + original_status = status_from_object - return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status) + + return reject_payload! if original_status.nil? || !announceable?(original_status) status = Status.find_by(account: @account, reblog: original_status) @@ -39,4 +42,12 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity def announceable?(status) status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility? end + + def related_to_local_activity? + followed_by_local_accounts? || requested_through_relay? || reblog_of_local_status? + end + + def reblog_of_local_status? + status_from_uri(object_uri)&.account&.local? + end end diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 1b31768d9..d7bd65c80 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -2,7 +2,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def perform - return if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity? + return reject_payload! if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity? RedisLock.acquire(lock_options) do |lock| if lock.acquired? @@ -341,18 +341,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity responds_to_followed_account? || addresses_local_accounts? end - def fetch? - !@options[:delivery] - end - - def followed_by_local_accounts? - @account.passive_relationships.exists? - end - - def requested_through_relay? - @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled? - end - def responds_to_followed_account? !replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?) end diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index a9316cd4b..ab68219ad 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -2,7 +2,7 @@ class REST::ApplicationSerializer < ActiveModel::Serializer attributes :id, :name, :website, :redirect_uri, - :client_id, :client_secret + :client_id, :client_secret, :vapid_key def id object.id.to_s @@ -19,4 +19,8 @@ class REST::ApplicationSerializer < ActiveModel::Serializer def website object.website.presence end + + def vapid_key + Rails.configuration.x.vapid_public_key + end end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index cab05e60a..41ed1995d 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer attributes :uri, :title, :description, :email, :version, :urls, :stats, :thumbnail, :max_toot_chars, - :languages + :languages, :registrations has_one :contact_account, serializer: REST::AccountSerializer @@ -55,6 +55,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer [I18n.default_locale] end + def registrations + Setting.open_registrations && !Rails.configuration.x.single_user_mode + end + private def instance_presenter diff --git a/app/views/layouts/error.html.haml b/app/views/layouts/error.html.haml index d662d85e2..f8315afb5 100644 --- a/app/views/layouts/error.html.haml +++ b/app/views/layouts/error.html.haml @@ -5,10 +5,12 @@ %meta{ charset: 'utf-8' }/ %title= safe_join([yield(:page_title), Setting.default_settings['site_title']], ' - ') %meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/ + = javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous' = render partial: 'layouts/theme', object: (@core || { pack: 'common' }) - = render partial: 'layouts/theme', object: (@theme || { pack: 'common', flavour: 'glitch', skin: 'default' }) + = render partial: 'layouts/theme', object: (@theme || { pack: 'error', flavour: 'glitch', common: { pack: 'common', flavour: 'glitch', skin: 'default' } }) %body.error .dialog - %img{ alt: Setting.default_settings['site_title'], src: current_user&.setting_auto_play_gif ? '/oops.gif' : '/oops.png' }/ - %div + .dialog__illustration + %img{ alt: Setting.default_settings['site_title'], src: '/oops.png' }/ + .dialog__message %h1= yield :content |