Fix tests, add applications to eager loading/cache for statuses, fix
application website validation, don't link to app website if website isn't set, also comment out animated boost icon from #464 until it's consistent with non-animated version
This commit is contained in:
		| @@ -87,3 +87,4 @@ AllCops: | |||||||
|   - 'bin/*' |   - 'bin/*' | ||||||
|   - 'Rakefile' |   - 'Rakefile' | ||||||
|   - 'node_modules/**/*' |   - 'node_modules/**/*' | ||||||
|  |   - 'Vagrantfile' | ||||||
|   | |||||||
| @@ -32,7 +32,9 @@ const DetailedStatus = React.createClass({ | |||||||
|  |  | ||||||
|   render () { |   render () { | ||||||
|     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; |     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; | ||||||
|  |  | ||||||
|     let media           = ''; |     let media           = ''; | ||||||
|  |     let applicationLink = ''; | ||||||
|  |  | ||||||
|     if (status.get('media_attachments').size > 0) { |     if (status.get('media_attachments').size > 0) { | ||||||
|       if (status.getIn(['media_attachments', 0, 'type']) === 'video') { |       if (status.getIn(['media_attachments', 0, 'type']) === 'video') { | ||||||
| @@ -42,6 +44,10 @@ const DetailedStatus = React.createClass({ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (status.get('application')) { | ||||||
|  |       applicationLink = <span> · <a className='detailed-status__application' style={{ color: 'inherit' }} href={status.getIn(['application', 'website'])} target='_blank' rel='nooopener'>{status.getIn(['application', 'name'])}</a></span>; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ background: '#2f3441', padding: '14px 10px' }} className='detailed-status'> |       <div style={{ background: '#2f3441', padding: '14px 10px' }} className='detailed-status'> | ||||||
|         <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}> |         <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}> | ||||||
| @@ -54,7 +60,7 @@ const DetailedStatus = React.createClass({ | |||||||
|         {media} |         {media} | ||||||
|  |  | ||||||
|         <div style={{ marginTop: '15px', color: '#616b86', fontSize: '14px', lineHeight: '18px' }}> |         <div style={{ marginTop: '15px', color: '#616b86', fontSize: '14px', lineHeight: '18px' }}> | ||||||
|           <a className='detailed-status__datetime' style={{ color: 'inherit' }} href={status.get('url')} target='_blank' rel='noopener'><FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /></a> · <a className='detailed-status__application' style={{ color: 'inherit' }} href={status.getIn(['application', 'website'])} target='_blank' rel='nooopener'>{status.getIn(['application', 'name'])}</a> · <Link to={`/statuses/${status.get('id')}/reblogs`} style={{ color: 'inherit', textDecoration: 'none' }}><i className='fa fa-retweet' /><span style={{ fontWeight: '500', fontSize: '12px', marginLeft: '6px', display: 'inline-block' }}><FormattedNumber value={status.get('reblogs_count')} /></span></Link> · <Link to={`/statuses/${status.get('id')}/favourites`} style={{ color: 'inherit', textDecoration: 'none' }}><i className='fa fa-star' /><span style={{ fontWeight: '500', fontSize: '12px', marginLeft: '6px', display: 'inline-block' }}><FormattedNumber value={status.get('favourites_count')} /></span></Link> |           <a className='detailed-status__datetime' style={{ color: 'inherit' }} href={status.get('url')} target='_blank' rel='noopener'><FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /></a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} style={{ color: 'inherit', textDecoration: 'none' }}><i className='fa fa-retweet' /><span style={{ fontWeight: '500', fontSize: '12px', marginLeft: '6px', display: 'inline-block' }}><FormattedNumber value={status.get('reblogs_count')} /></span></Link> · <Link to={`/statuses/${status.get('id')}/favourites`} style={{ color: 'inherit', textDecoration: 'none' }}><i className='fa fa-star' /><span style={{ fontWeight: '500', fontSize: '12px', marginLeft: '6px', display: 'inline-block' }}><FormattedNumber value={status.get('favourites_count')} /></span></Link> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -663,20 +663,21 @@ | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| button i.fa-retweet { | // Commented out until sprite matches non-sprite icon visually | ||||||
|   height: 19px; | // button i.fa-retweet { | ||||||
|   width: 24px; | //   height: 19px; | ||||||
|   background: image-url('boost_sprite.png') no-repeat; | //   width: 24px; | ||||||
|   background-position: 0 0; | //   background: image-url('boost_sprite.png') no-repeat; | ||||||
|   transition: background-position 0.9s steps(11); | //   background-position: 0 0; | ||||||
|   transition-duration: 0s; | //   transition: background-position 0.9s steps(11); | ||||||
|  | //   transition-duration: 0s; | ||||||
|  |  | ||||||
|   &::before { | //   &::before { | ||||||
|     display: none !important; | //     display: none !important; | ||||||
|   } | //   } | ||||||
| } | // } | ||||||
|  |  | ||||||
| button.active i.fa-retweet { | // button.active i.fa-retweet { | ||||||
|   transition-duration: 0.9s; | //   transition-duration: 0.9s; | ||||||
|   background-position: 0 -209px; | //   background-position: 0 -209px; | ||||||
| } | // } | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								app/lib/application_extension.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/lib/application_extension.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  |  | ||||||
|  | module ApplicationExtension | ||||||
|  |   extend ActiveSupport::Concern | ||||||
|  |  | ||||||
|  |   included do | ||||||
|  |     validates :website, url: true, unless: 'website.blank?' | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										14
									
								
								app/lib/url_validator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/lib/url_validator.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  |  | ||||||
|  | class UrlValidator < ActiveModel::EachValidator | ||||||
|  |   def validate_each(record, attribute, value) | ||||||
|  |     record.errors.add(attribute, I18n.t('applications.invalid_url')) unless compliant?(value) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def compliant?(url) | ||||||
|  |     parsed_url = Addressable::URI.parse(url) | ||||||
|  |     !parsed_url.nil? && %w(http https).include?(parsed_url.scheme) && parsed_url.host | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| module ApplicationExtension |  | ||||||
|   extend ActiveSupport::Concern |  | ||||||
|   included do |  | ||||||
|     validates :website |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  |  | ||||||
| Doorkeeper::Application.send :include, ApplicationExtension |  | ||||||
| @@ -35,7 +35,7 @@ class Status < ApplicationRecord | |||||||
|   scope :remote, -> { where.not(uri: nil) } |   scope :remote, -> { where.not(uri: nil) } | ||||||
|   scope :local, -> { where(uri: nil) } |   scope :local, -> { where(uri: nil) } | ||||||
|  |  | ||||||
|   cache_associated :account, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account |   cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account | ||||||
|  |  | ||||||
|   def local? |   def local? | ||||||
|     uri.nil? |     uri.nil? | ||||||
|   | |||||||
| @@ -7,10 +7,17 @@ class PostStatusService < BaseService | |||||||
|   # @param [Status] in_reply_to Optional status to reply to |   # @param [Status] in_reply_to Optional status to reply to | ||||||
|   # @param [Hash] options |   # @param [Hash] options | ||||||
|   # @option [Boolean] :sensitive |   # @option [Boolean] :sensitive | ||||||
|  |   # @option [String] :visibility | ||||||
|   # @option [Enumerable] :media_ids Optional array of media IDs to attach |   # @option [Enumerable] :media_ids Optional array of media IDs to attach | ||||||
|  |   # @option [Doorkeeper::Application] :application | ||||||
|   # @return [Status] |   # @return [Status] | ||||||
|   def call(account, text, in_reply_to = nil, options = {}) |   def call(account, text, in_reply_to = nil, options = {}) | ||||||
|     status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:visibility], application: options[:application]) |     status = account.statuses.create!(text:        text, | ||||||
|  |                                       thread:      in_reply_to, | ||||||
|  |                                       sensitive:   options[:sensitive], | ||||||
|  |                                       visibility:  options[:visibility], | ||||||
|  |                                       application: options[:application]) | ||||||
|  |  | ||||||
|     attach_media(status, options[:media_ids]) |     attach_media(status, options[:media_ids]) | ||||||
|     process_mentions_service.call(status) |     process_mentions_service.call(status) | ||||||
|     process_hashtags_service.call(status) |     process_hashtags_service.call(status) | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| object @application | object @application | ||||||
|  |  | ||||||
| attributes :id, :name, :website | attributes :name, :website | ||||||
|   | |||||||
| @@ -29,13 +29,15 @@ | |||||||
|       %span= l(status.created_at) |       %span= l(status.created_at) | ||||||
|     · |     · | ||||||
|     - if status.application |     - if status.application | ||||||
|       = link_to status.application.website, class: 'detailed-status__application', target: @external_links ? '_blank' : nil, rel: 'noopener' do |       - if status.application.website.blank? | ||||||
|         %span= status.application.name |         %strong.detailed-status__application= status.application.name | ||||||
|  |       - else | ||||||
|  |         = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener' | ||||||
|       · |       · | ||||||
|     %span |     %span< | ||||||
|       = fa_icon('retweet') |       = fa_icon('retweet') | ||||||
|       %span= status.reblogs.count |       %span= status.reblogs.count | ||||||
|     · |     · | ||||||
|     %span |     %span< | ||||||
|       = fa_icon('star') |       = fa_icon('star') | ||||||
|       %span= status.favourites.count |       %span= status.favourites.count | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ module Mastodon | |||||||
|  |  | ||||||
|     config.to_prepare do |     config.to_prepare do | ||||||
|       Doorkeeper::AuthorizationsController.layout 'public' |       Doorkeeper::AuthorizationsController.layout 'public' | ||||||
|  |       Doorkeeper::Application.send :include, ApplicationExtension | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     config.action_dispatch.default_headers = { |     config.action_dispatch.default_headers = { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ en: | |||||||
|     domain_count_after: other instances |     domain_count_after: other instances | ||||||
|     domain_count_before: Connected to |     domain_count_before: Connected to | ||||||
|     get_started: Get started |     get_started: Get started | ||||||
|  |     learn_more: Learn more | ||||||
|     links: Links |     links: Links | ||||||
|     source_code: Source code |     source_code: Source code | ||||||
|     status_count_after: statuses |     status_count_after: statuses | ||||||
| @@ -15,7 +16,6 @@ en: | |||||||
|     terms: Terms |     terms: Terms | ||||||
|     user_count_after: users |     user_count_after: users | ||||||
|     user_count_before: Home to |     user_count_before: Home to | ||||||
|     learn_more: Learn more |  | ||||||
|   accounts: |   accounts: | ||||||
|     follow: Follow |     follow: Follow | ||||||
|     followers: Followers |     followers: Followers | ||||||
| @@ -28,6 +28,8 @@ en: | |||||||
|     unfollow: Unfollow |     unfollow: Unfollow | ||||||
|   application_mailer: |   application_mailer: | ||||||
|     signature: Mastodon notifications from %{instance} |     signature: Mastodon notifications from %{instance} | ||||||
|  |   applications: | ||||||
|  |     invalid_url: The provided URL is invalid | ||||||
|   auth: |   auth: | ||||||
|     change_password: Change password |     change_password: Change password | ||||||
|     didnt_get_confirmation: Didn't receive confirmation instructions? |     didnt_get_confirmation: Didn't receive confirmation instructions? | ||||||
| @@ -88,9 +90,9 @@ en: | |||||||
|     proceed: Proceed to follow |     proceed: Proceed to follow | ||||||
|     prompt: 'You are going to follow:' |     prompt: 'You are going to follow:' | ||||||
|   settings: |   settings: | ||||||
|  |     back: Back to Mastodon | ||||||
|     edit_profile: Edit profile |     edit_profile: Edit profile | ||||||
|     preferences: Preferences |     preferences: Preferences | ||||||
|     back: Back to Mastodon |  | ||||||
|   stream_entries: |   stream_entries: | ||||||
|     click_to_show: Click to show |     click_to_show: Click to show | ||||||
|     favourited: favourited a post by |     favourited: favourited a post by | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ RSpec.describe Api::V1::StatusesController, type: :controller do | |||||||
|   render_views |   render_views | ||||||
|  |  | ||||||
|   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } |   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | ||||||
|   let(:token) { double acceptable?: true, resource_owner_id: user.id } |   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } | ||||||
|  |   let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app } | ||||||
|  |  | ||||||
|   before do |   before do | ||||||
|     allow(controller).to receive(:doorkeeper_token) { token } |     allow(controller).to receive(:doorkeeper_token) { token } | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								spec/fabricators/application_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/fabricators/application_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | Fabricator(:application, from: Doorkeeper::Application) do | ||||||
|  |   name         'Example' | ||||||
|  |   website      'http://example.com' | ||||||
|  |   redirect_uri 'http://example.com/callback' | ||||||
|  | end | ||||||
		Reference in New Issue
	
	Block a user