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:
parent
ab165547fd
commit
e9737c2235
|
@ -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
|
Loading…
Reference in a new issue