commit: bfec9aaee077c6dd54081a89f697271d7a5c0a6a
parent: 1f7c0ad8d3d336b036d16272716e41812f65f5d9
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Sun, 15 Jan 2017 14:04:56 +0100
Merge branch 'ineffyble-feature/toot-app-source'
Diffstat:
19 files changed, 102 insertions(+), 28 deletions(-)
diff --git a/.rubocop.yml b/.rubocop.yml
@@ -87,3 +87,4 @@ AllCops:
- 'bin/*'
- 'Rakefile'
- 'node_modules/**/*'
+ - 'Vagrantfile'
diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
@@ -32,7 +32,9 @@ const DetailedStatus = React.createClass({
render () {
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.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 (
<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' }}>
@@ -54,7 +60,7 @@ const DetailedStatus = React.createClass({
{media}
<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> · <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>
);
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
@@ -183,7 +183,7 @@
}
}
-.status__display-name, .status__relative-time, .detailed-status__display-name, .detailed-status__datetime, .account__display-name {
+.status__display-name, .status__relative-time, .detailed-status__display-name, .detailed-status__datetime, .detailed-status__application, .account__display-name {
text-decoration: none;
}
@@ -663,20 +663,21 @@
}
}
-button i.fa-retweet {
- height: 19px;
- width: 24px;
- background: image-url('boost_sprite.png') no-repeat;
- background-position: 0 0;
- transition: background-position 0.9s steps(11);
- transition-duration: 0s;
+// Commented out until sprite matches non-sprite icon visually
+// button i.fa-retweet {
+// height: 19px;
+// width: 24px;
+// background: image-url('boost_sprite.png') no-repeat;
+// background-position: 0 0;
+// transition: background-position 0.9s steps(11);
+// transition-duration: 0s;
- &::before {
- display: none !important;
- }
-}
+// &::before {
+// display: none !important;
+// }
+// }
-button.active i.fa-retweet {
- transition-duration: 0.9s;
- background-position: 0 -209px;
-}
+// button.active i.fa-retweet {
+// transition-duration: 0.9s;
+// background-position: 0 -209px;
+// }
diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb
@@ -4,6 +4,6 @@ class Api::V1::AppsController < ApiController
respond_to :json
def create
- @app = Doorkeeper::Application.create!(name: params[:client_name], redirect_uri: params[:redirect_uris], scopes: (params[:scopes] || Doorkeeper.configuration.default_scopes))
+ @app = Doorkeeper::Application.create!(name: params[:client_name], redirect_uri: params[:redirect_uris], scopes: (params[:scopes] || Doorkeeper.configuration.default_scopes), website: params[:website])
end
end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
@@ -52,7 +52,7 @@ class Api::V1::StatusesController < ApiController
end
def create
- @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], visibility: params[:visibility])
+ @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], visibility: params[:visibility], application: doorkeeper_token.application)
render action: :show
end
diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module ApplicationExtension
+ extend ActiveSupport::Concern
+
+ included do
+ validates :website, url: true, unless: 'website.blank?'
+ end
+end
diff --git a/app/lib/url_validator.rb b/app/lib/url_validator.rb
@@ -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
diff --git a/app/models/status.rb b/app/models/status.rb
@@ -7,6 +7,8 @@ class Status < ApplicationRecord
enum visibility: [:public, :unlisted, :private], _suffix: :visibility
+ belongs_to :application, class_name: 'Doorkeeper::Application'
+
belongs_to :account, inverse_of: :statuses
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
@@ -33,7 +35,7 @@ class Status < ApplicationRecord
scope :remote, -> { where.not(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?
uri.nil?
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
@@ -7,10 +7,17 @@ class PostStatusService < BaseService
# @param [Status] in_reply_to Optional status to reply to
# @param [Hash] options
# @option [Boolean] :sensitive
+ # @option [String] :visibility
# @option [Enumerable] :media_ids Optional array of media IDs to attach
+ # @option [Doorkeeper::Application] :application
# @return [Status]
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])
+ 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])
process_mentions_service.call(status)
process_hashtags_service.call(status)
diff --git a/app/views/api/v1/apps/show.rabl b/app/views/api/v1/apps/show.rabl
@@ -0,0 +1,3 @@
+object @application
+
+attributes :name, :website
diff --git a/app/views/api/v1/statuses/_show.rabl b/app/views/api/v1/statuses/_show.rabl
@@ -6,6 +6,10 @@ node(:url) { |status| TagManager.instance.url_for(status) }
node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs.count }
node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites.count }
+child :application do
+ extends 'api/v1/apps/show'
+end
+
child :account do
extends 'api/v1/accounts/show'
end
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
@@ -28,10 +28,16 @@
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: @external_links ? '_blank' : nil, rel: 'noopener' do
%span= l(status.created_at)
·
- %span
+ - if status.application
+ - if status.application.website.blank?
+ %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<
= fa_icon('retweet')
%span= status.reblogs.count
·
- %span
+ %span<
= fa_icon('star')
%span= status.favourites.count
diff --git a/config/application.rb b/config/application.rb
@@ -46,6 +46,7 @@ module Mastodon
config.to_prepare do
Doorkeeper::AuthorizationsController.layout 'public'
+ Doorkeeper::Application.send :include, ApplicationExtension
end
config.action_dispatch.default_headers = {
diff --git a/config/locales/en.yml b/config/locales/en.yml
@@ -8,6 +8,7 @@ en:
domain_count_after: other instances
domain_count_before: Connected to
get_started: Get started
+ learn_more: Learn more
links: Links
source_code: Source code
status_count_after: statuses
@@ -15,7 +16,6 @@ en:
terms: Terms
user_count_after: users
user_count_before: Home to
- learn_more: Learn more
accounts:
follow: Follow
followers: Followers
@@ -28,6 +28,8 @@ en:
unfollow: Unfollow
application_mailer:
signature: Mastodon notifications from %{instance}
+ applications:
+ invalid_url: The provided URL is invalid
auth:
change_password: Change password
didnt_get_confirmation: Didn't receive confirmation instructions?
@@ -88,9 +90,9 @@ en:
proceed: Proceed to follow
prompt: 'You are going to follow:'
settings:
+ back: Back to Mastodon
edit_profile: Edit profile
preferences: Preferences
- back: Back to Mastodon
stream_entries:
click_to_show: Click to show
favourited: favourited a post by
diff --git a/db/migrate/20170114194937_add_application_to_statuses.rb b/db/migrate/20170114194937_add_application_to_statuses.rb
@@ -0,0 +1,5 @@
+class AddApplicationToStatuses < ActiveRecord::Migration[5.0]
+ def change
+ add_column :statuses, :application_id, :int
+ end
+end
diff --git a/db/migrate/20170114203041_add_website_to_oauth_application.rb b/db/migrate/20170114203041_add_website_to_oauth_application.rb
@@ -0,0 +1,5 @@
+class AddWebsiteToOauthApplication < ActiveRecord::Migration[5.0]
+ def change
+ add_column :oauth_applications, :website, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170112154826) do
+ActiveRecord::Schema.define(version: 20170114203041) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -153,6 +153,7 @@ ActiveRecord::Schema.define(version: 20170112154826) do
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "superapp", default: false, null: false
+ t.string "website"
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
end
@@ -259,6 +260,7 @@ ActiveRecord::Schema.define(version: 20170112154826) do
t.boolean "sensitive", default: false
t.integer "visibility", default: 0, null: false
t.integer "in_reply_to_account_id"
+ t.integer "application_id"
t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree
t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -4,7 +4,8 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
render_views
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
allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/fabricators/application_fabricator.rb b/spec/fabricators/application_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:application, from: Doorkeeper::Application) do
+ name 'Example'
+ website 'http://example.com'
+ redirect_uri 'http://example.com/callback'
+end