Merge tag 'v2.7.1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira 2019-01-31 11:08:42 +01:00
commit e8012c10be
18 changed files with 170 additions and 30 deletions

View File

@ -3,6 +3,20 @@ Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [2.7.1] - 2019-01-28
### Fixed
- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/tootsuite/mastodon/pull/9915))
- Fix slow fallback of CopyAccountStats migration setting stats to 0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9930))
- Fix wrong command in migration error message ([angristan](https://github.com/tootsuite/mastodon/pull/9877))
- Fix initial value of volume slider in video player and handle volume changes ([ThibG](https://github.com/tootsuite/mastodon/pull/9929))
- Fix missing hotkeys for notifications ([ThibG](https://github.com/tootsuite/mastodon/pull/9927))
- Fix being able to attach unattached media created by other users ([ThibG](https://github.com/tootsuite/mastodon/pull/9921))
- Fix unrescued SSL error during link verification ([renatolond](https://github.com/tootsuite/mastodon/pull/9914))
- Fix Firefox scrollbar color regression ([trwnh](https://github.com/tootsuite/mastodon/pull/9908))
- Fix scheduled status with media immediately creating a status ([ThibG](https://github.com/tootsuite/mastodon/pull/9894))
- Fix missing strong style for landing page description ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9892))
## [2.7.0] - 2019-01-20 ## [2.7.0] - 2019-01-20
### Added ### Added

View File

@ -1,7 +1,7 @@
Contributing Contributing
============ ============
Thank you for considering contributing to Mastodon 🐘 Thank you for considering contributing to Mastodon 🐘
You can contribute in the following ways: You can contribute in the following ways:
@ -10,6 +10,8 @@ You can contribute in the following ways:
- Contributing code to Mastodon by fixing bugs or implementing features - Contributing code to Mastodon by fixing bugs or implementing features
- Improving the documentation - Improving the documentation
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
## Bug reports ## Bug reports
Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles. Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles.

View File

@ -23,7 +23,7 @@ gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0' gem 'streamio-ffmpeg', '~> 3.0'
gem 'active_model_serializers', '~> 0.10' gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.5' gem 'addressable', '~> 2.6'
gem 'bootsnap', '~> 1.3', require: false gem 'bootsnap', '~> 1.3', require: false
gem 'browser' gem 'browser'
gem 'charlock_holmes', '~> 0.7.6' gem 'charlock_holmes', '~> 0.7.6'

View File

@ -62,7 +62,7 @@ GEM
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.5.2) addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0) public_suffix (>= 2.0.2, < 4.0)
airbrussh (1.3.0) airbrussh (1.3.0)
sshkit (>= 1.6.1, != 1.7.0) sshkit (>= 1.6.1, != 1.7.0)
@ -290,8 +290,8 @@ GEM
json-ld (3.0.2) json-ld (3.0.2)
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (>= 2.2.8, < 4.0) rdf (>= 2.2.8, < 4.0)
json-ld-preloaded (3.0.0) json-ld-preloaded (3.0.2)
json-ld (>= 2.2, < 4.0) json-ld (~> 3.0)
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (~> 3.0) rdf (~> 3.0)
jsonapi-renderer (0.2.0) jsonapi-renderer (0.2.0)
@ -363,7 +363,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.7.7) oj (3.7.8)
omniauth (1.9.0) omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0) hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -389,7 +389,7 @@ GEM
paperclip-av-transcoder (0.6.4) paperclip-av-transcoder (0.6.4)
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.12.1) parallel (1.13.0)
parallel_tests (2.27.1) parallel_tests (2.27.1)
parallel parallel
parser (2.6.0.0) parser (2.6.0.0)
@ -420,7 +420,7 @@ GEM
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (3.0.3) public_suffix (3.0.3)
puma (3.12.0) puma (3.12.0)
pundit (2.0.0) pundit (2.0.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.1.6) raabro (1.1.6)
rack (2.0.6) rack (2.0.6)
@ -513,7 +513,7 @@ GEM
rspec-mocks (3.8.0) rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.8.0)
rspec-rails (3.8.1) rspec-rails (3.8.2)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
@ -525,7 +525,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.8.0) rspec-support (3.8.0)
rubocop (0.63.0) rubocop (0.63.1)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1) parser (>= 2.5, != 2.5.1.1)
@ -655,7 +655,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
active_model_serializers (~> 0.10) active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.5) active_record_query_trace (~> 1.5)
addressable (~> 2.5) addressable (~> 2.6)
annotate (~> 2.7) annotate (~> 2.7)
aws-sdk-s3 (~> 1.30) aws-sdk-s3 (~> 1.30)
better_errors (~> 2.5) better_errors (~> 2.5)

View File

@ -21,7 +21,7 @@ Click below to **learn more** in a video:
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE [youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
## Navigation ## Navigation
- [Project homepage 🐘](https://joinmastodon.org) - [Project homepage 🐘](https://joinmastodon.org)
- [Support the development via Patreon][patreon] - [Support the development via Patreon][patreon]
@ -80,7 +80,7 @@ A **Vagrant** configuration is included for development purposes.
Mastodon is **free, open source software** licensed under **AGPLv3**. Mastodon is **free, open source software** licensed under **AGPLv3**.
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md) You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
**IRC channel**: #mastodon on irc.freenode.net **IRC channel**: #mastodon on irc.freenode.net

View File

@ -29,6 +29,10 @@ class Notification extends ImmutablePureComponent {
onMoveUp: PropTypes.func.isRequired, onMoveUp: PropTypes.func.isRequired,
onMoveDown: PropTypes.func.isRequired, onMoveDown: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
status: PropTypes.option,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -64,14 +68,32 @@ class Notification extends ImmutablePureComponent {
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), this.context.router.history);
} }
handleHotkeyFavourite = () => {
const { status } = this.props;
if (status) this.props.onFavourite(status);
}
handleHotkeyBoost = e => {
const { status } = this.props;
if (status) this.props.onReblog(status, e);
}
handleHotkeyToggleHidden = () => {
const { status } = this.props;
if (status) this.props.onToggleHidden(status);
}
getHandlers () { getHandlers () {
return { return {
moveUp: this.handleMoveUp, reply: this.handleMention,
moveDown: this.handleMoveDown, favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleMention,
open: this.handleOpen, open: this.handleOpen,
openProfile: this.handleOpenProfile, openProfile: this.handleOpenProfile,
mention: this.handleMention, moveUp: this.handleMoveUp,
reply: this.handleMention, moveDown: this.handleMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
}; };
} }

View File

@ -1,14 +1,31 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeGetNotification } from '../../../selectors'; import { makeGetNotification, makeGetStatus } from '../../../selectors';
import Notification from '../components/notification'; import Notification from '../components/notification';
import { openModal } from '../../../actions/modal';
import { mentionCompose } from '../../../actions/compose'; import { mentionCompose } from '../../../actions/compose';
import {
reblog,
favourite,
unreblog,
unfavourite,
} from '../../../actions/interactions';
import {
hideStatus,
revealStatus,
} from '../../../actions/statuses';
import { boostModal } from '../../../initial_state';
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
const getNotification = makeGetNotification(); const getNotification = makeGetNotification();
const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => {
notification: getNotification(state, props.notification, props.accountId), const notification = getNotification(state, props.notification, props.accountId);
}); return {
notification: notification,
status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null,
};
};
return mapStateToProps; return mapStateToProps;
}; };
@ -17,6 +34,38 @@ const mapDispatchToProps = dispatch => ({
onMention: (account, router) => { onMention: (account, router) => {
dispatch(mentionCompose(account, router)); dispatch(mentionCompose(account, router));
}, },
onModalReblog (status) {
dispatch(reblog(status));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
}
}
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
} else {
dispatch(favourite(status));
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
}); });
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);

View File

@ -136,6 +136,9 @@ class Video extends React.PureComponent {
setVideoRef = c => { setVideoRef = c => {
this.video = c; this.video = c;
if (this.video) {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
} }
setSeekRef = c => { setSeekRef = c => {
@ -302,6 +305,10 @@ class Video extends React.PureComponent {
} }
} }
handleVolumeChange = () => {
this.setState({ volume: this.video.volume, muted: this.video.muted });
}
handleOpenVideo = () => { handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props; const { src, preview, width, height, alt } = this.props;
const media = fromJS({ const media = fromJS({
@ -387,6 +394,7 @@ class Video extends React.PureComponent {
onTimeUpdate={this.handleTimeUpdate} onTimeUpdate={this.handleTimeUpdate}
onLoadedData={this.handleLoadedData} onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress} onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange}
/> />
<button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}> <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
@ -409,7 +417,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'> <div className='video-player__buttons-bar'>
<div className='video-player__buttons left'> <div className='video-player__buttons left'>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onMouseEnter={this.volumeSlider} onMouseLeave={this.volumeSlider} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}> <div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} /> <div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
<span <span

View File

@ -845,6 +845,18 @@ $small-breakpoint: 960px;
margin-bottom: 0; margin-bottom: 0;
} }
strong {
display: inline;
margin: 0;
padding: 0;
font-weight: 700;
background: transparent;
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: lighten($darker-text-color, 10%);
}
.account { .account {
border-bottom: 0; border-bottom: 0;
padding: 0; padding: 0;

View File

@ -54,7 +54,7 @@ table {
} }
html { html {
scrollbar-color: lighten($ui-base-color, 4%) transparent; scrollbar-color: lighten($ui-base-color, 4%) rgba($base-overlay-background, 0.1);
} }
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -63,6 +63,7 @@ module Omniauthable
{ {
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com", email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0, 20], password: Devise.friendly_token[0, 20],
agreement: true,
account_attributes: { account_attributes: {
username: ensure_unique_username(auth.uid), username: ensure_unique_username(auth.uid),
display_name: display_name, display_name: display_name,

View File

@ -295,6 +295,7 @@ class User < ApplicationRecord
def self.pam_get_user(attributes = {}) def self.pam_get_user(attributes = {})
return nil unless attributes[:email] return nil unless attributes[:email]
resource = resource =
if Devise.check_at_sign && !attributes[:email].index('@') if Devise.check_at_sign && !attributes[:email].index('@')
joins(:account).find_by(accounts: { username: attributes[:email] }) joins(:account).find_by(accounts: { username: attributes[:email] })
@ -304,6 +305,7 @@ class User < ApplicationRecord
if resource.blank? if resource.blank?
resource = new(email: attributes[:email], agreement: true) resource = new(email: attributes[:email], agreement: true)
if Devise.check_at_sign && !resource[:email].index('@') if Devise.check_at_sign && !resource[:email].index('@')
resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false) resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email] resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]

View File

@ -66,7 +66,10 @@ class PostStatusService < BaseService
end end
def schedule_status! def schedule_status!
if @account.statuses.build(status_attributes).valid? status_for_validation = @account.statuses.build(status_attributes)
if status_for_validation.valid?
status_for_validation.destroy
# The following transaction block is needed to wrap the UPDATEs to # The following transaction block is needed to wrap the UPDATEs to
# the media attachments when the scheduled status is created # the media attachments when the scheduled status is created
@ -99,7 +102,7 @@ class PostStatusService < BaseService
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4
@media = MediaAttachment.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
end end

View File

@ -10,7 +10,7 @@ class VerifyLinkService < BaseService
return unless link_back_present? return unless link_back_present?
field.mark_verified! field.mark_verified!
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.debug "Error fetching link #{@url}: #{e}" Rails.logger.debug "Error fetching link #{@url}: #{e}"
nil nil
end end

View File

@ -44,7 +44,7 @@ class CopyAccountStats < ActiveRecord::Migration[5.2]
# uniqueness violations that we need to skip over # uniqueness violations that we need to skip over
Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account| Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account|
begin begin
params = [[nil, account.id], [nil, account.statuses_count], [nil, account.following_count], [nil, account.followers_count], [nil, account.created_at], [nil, account.updated_at]] params = [[nil, account.id], [nil, account[:statuses_count]], [nil, account[:following_count]], [nil, account[:followers_count]], [nil, account.created_at], [nil, account.updated_at]]
exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params) exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params)
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
next next

View File

@ -889,7 +889,7 @@ table #{table}.
If you are using PostgreSQL you can solve this by logging in to the GitLab If you are using PostgreSQL you can solve this by logging in to the GitLab
database (#{dbname}) using a super user and running: database (#{dbname}) using a super user and running:
ALTER #{user} WITH SUPERUSER ALTER USER #{user} WITH SUPERUSER
For MySQL you instead need to run: For MySQL you instead need to run:

View File

@ -13,7 +13,7 @@ module Mastodon
end end
def patch def patch
0 1
end end
def pre def pre

View File

@ -36,6 +36,20 @@ RSpec.describe PostStatusService, type: :service do
expect(status.params['text']).to eq 'Hi future!' expect(status.params['text']).to eq 'Hi future!'
end end
it 'does not immediately create a status when scheduling a status' do
account = Fabricate(:account)
media = Fabricate(:media_attachment)
future = Time.now.utc + 2.hours
status = subject.call(account, text: 'Hi future!', media_ids: [media.id], scheduled_at: future)
expect(status).to be_a ScheduledStatus
expect(status.scheduled_at).to eq future
expect(status.params['text']).to eq 'Hi future!'
expect(media.reload.status).to be_nil
expect(Status.where(text: 'Hi future!').exists?).to be_falsey
end
it 'creates response to the original status of boost' do it 'creates response to the original status of boost' do
boosted_status = Fabricate(:status) boosted_status = Fabricate(:status)
in_reply_to_status = Fabricate(:status, reblog: boosted_status) in_reply_to_status = Fabricate(:status, reblog: boosted_status)
@ -153,7 +167,7 @@ RSpec.describe PostStatusService, type: :service do
it 'attaches the given media to the created status' do it 'attaches the given media to the created status' do
account = Fabricate(:account) account = Fabricate(:account)
media = Fabricate(:media_attachment) media = Fabricate(:media_attachment, account: account)
status = subject.call( status = subject.call(
account, account,
@ -164,6 +178,19 @@ RSpec.describe PostStatusService, type: :service do
expect(media.reload.status).to eq status expect(media.reload.status).to eq status
end end
it 'does not attach media from another account to the created status' do
account = Fabricate(:account)
media = Fabricate(:media_attachment, account: Fabricate(:account))
status = subject.call(
account,
text: "test status update",
media_ids: [media.id],
)
expect(media.reload.status).to eq nil
end
it 'does not allow attaching more than 4 files' do it 'does not allow attaching more than 4 files' do
account = Fabricate(:account) account = Fabricate(:account)