Merge pull request #1249 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
master
ThibG 5 years ago committed by GitHub
commit 7f6315841c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      Dockerfile
  2. 10
      Gemfile
  3. 24
      Gemfile.lock
  4. 2
      app/controllers/api/proofs_controller.rb
  5. 5
      app/javascript/flavours/glitch/actions/compose.js
  6. 17
      app/javascript/flavours/glitch/components/status.js
  7. 4
      app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
  8. 17
      app/javascript/flavours/glitch/features/status/index.js
  9. 2
      app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
  10. 1
      app/javascript/flavours/glitch/features/ui/index.js
  11. 2
      app/javascript/flavours/glitch/features/video/index.js
  12. 2
      app/javascript/flavours/glitch/reducers/compose.js
  13. 2
      app/javascript/flavours/glitch/styles/containers.scss
  14. 5
      app/javascript/mastodon/actions/compose.js
  15. 17
      app/javascript/mastodon/components/status.js
  16. 4
      app/javascript/mastodon/features/keyboard_shortcuts/index.js
  17. 17
      app/javascript/mastodon/features/status/index.js
  18. 2
      app/javascript/mastodon/features/ui/components/focal_point_modal.js
  19. 1
      app/javascript/mastodon/features/ui/index.js
  20. 2
      app/javascript/mastodon/features/video/index.js
  21. 2
      app/javascript/mastodon/reducers/compose.js
  22. 2
      app/javascript/styles/mastodon/containers.scss
  23. 3
      app/presenters/status_relationships_presenter.rb
  24. 4
      app/serializers/rest/status_serializer.rb
  25. 2
      app/services/activitypub/process_poll_service.rb
  26. 6
      app/views/settings/preferences/notifications/show.html.haml
  27. 4
      config/locales/en.yml
  28. 16
      config/locales/simple_form.en.yml
  29. 41
      config/pghero.yml
  30. 14
      package.json
  31. 923
      yarn.lock

@ -3,8 +3,8 @@ FROM ubuntu:18.04 as build-dep
# Use bash for the shell
SHELL ["bash", "-c"]
# Install Node
ENV NODE_VER="12.11.1"
# Install Node v12 (LTS)
ENV NODE_VER="12.13.1"
RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \
apt -y install wget python && \

@ -12,7 +12,7 @@ gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.1'
gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.3'
gem 'pghero', '~> 2.4'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.55', require: false
@ -27,7 +27,7 @@ gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.7'
gem 'bootsnap', '~> 1.4', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.6'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
gem 'chewy', '~> 5.1'
gem 'cld3', '~> 3.2.4'
@ -38,7 +38,7 @@ group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2'
end
gem 'net-ldap', '~> 0.10'
gem 'net-ldap', '~> 0.16'
gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9'
@ -68,12 +68,12 @@ gem 'oj', '~> 3.9'
gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.11'
gem 'parslet'
gem 'parallel', '~> 1.18'
gem 'parallel', '~> 1.19'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.1'
gem 'premailer-rails'
gem 'rack-attack', '~> 6.2'
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1'
gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']

@ -133,7 +133,7 @@ GEM
bootsnap (1.4.5)
msgpack (~> 1.0)
brakeman (4.7.1)
browser (2.6.1)
browser (2.7.1)
builder (3.2.3)
bullet (6.0.2)
activesupport (>= 3.0.0)
@ -168,7 +168,7 @@ GEM
xpath (~> 3.2)
case_transform (0.2)
activesupport
charlock_holmes (0.7.6)
charlock_holmes (0.7.7)
chewy (5.1.0)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
@ -385,7 +385,7 @@ GEM
multi_json (1.13.1)
multipart-post (2.1.1)
necromancer (0.5.0)
net-ldap (0.16.1)
net-ldap (0.16.2)
net-scp (2.0.0)
net-ssh (>= 2.6.5, < 6.0.0)
net-ssh (5.2.0)
@ -425,7 +425,7 @@ GEM
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.18.0)
parallel (1.19.1)
parallel_tests (2.29.2)
parallel
parser (2.6.5.0)
@ -435,7 +435,7 @@ GEM
equatable (~> 0.6)
tty-color (~> 0.5)
pg (1.1.4)
pghero (2.3.0)
pghero (2.4.1)
activerecord (>= 5)
pkg-config (1.4.0)
premailer (1.11.1)
@ -463,8 +463,8 @@ GEM
rack (2.0.7)
rack-attack (6.2.1)
rack (>= 1.0, < 3)
rack-cors (1.0.6)
rack (>= 1.6.0)
rack-cors (1.1.0)
rack (>= 2.0.0)
rack-protection (2.0.7)
rack
rack-proxy (0.6.5)
@ -699,7 +699,7 @@ DEPENDENCIES
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
capybara (~> 3.29)
charlock_holmes (~> 0.7.6)
charlock_holmes (~> 0.7.7)
chewy (~> 5.1)
cld3 (~> 3.2.4)
climate_control (~> 0.2)
@ -744,7 +744,7 @@ DEPENDENCIES
memory_profiler
microformats (~> 4.1)
mime-types (~> 3.3)
net-ldap (~> 0.10)
net-ldap (~> 0.16)
nilsimsa!
nokogiri (~> 1.10)
nsa (~> 0.2)
@ -756,11 +756,11 @@ DEPENDENCIES
ox (~> 2.11)
paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6)
parallel (~> 1.18)
parallel (~> 1.19)
parallel_tests (~> 2.29)
parslet
pg (~> 1.1)
pghero (~> 2.3)
pghero (~> 2.4)
pkg-config (~> 1.4)
posix-spawn!
premailer-rails
@ -770,7 +770,7 @@ DEPENDENCIES
puma (~> 4.2)
pundit (~> 2.1)
rack-attack (~> 6.2)
rack-cors (~> 1.0)
rack-cors (~> 1.1)
rails (~> 5.2.3)
rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1)

@ -3,6 +3,8 @@
class Api::ProofsController < Api::BaseController
include AccountOwnedConcern
skip_before_action :require_authenticated_user!
before_action :set_provider
def index

@ -263,7 +263,7 @@ export function uploadCompose(files) {
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
},
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).catch(error => dispatch(uploadComposeFail(error, true)));
}).catch(error => dispatch(uploadComposeFail(error)));
};
};
};
@ -294,11 +294,10 @@ export function changeUploadComposeSuccess(media) {
};
};
export function changeUploadComposeFail(error, decrement = false) {
export function changeUploadComposeFail(error) {
return {
type: COMPOSE_UPLOAD_CHANGE_FAIL,
error: error,
decrement: decrement,
skipLoading: true,
};
};

@ -376,6 +376,22 @@ class Status extends ImmutablePureComponent {
this.props.onOpenVideo(media, startTime);
}
handleHotkeyOpenMedia = e => {
const { status, onOpenMedia, onOpenVideo } = this.props;
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(status.getIn(['media_attachments', 0]), 0);
} else {
onOpenMedia(status.get('media_attachments'), 0);
}
}
}
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this.props.status, this.context.router.history);
@ -503,6 +519,7 @@ class Status extends ImmutablePureComponent {
bookmark: this.handleHotkeyBookmark,
toggleCollapse: this.handleHotkeyCollapse,
toggleSensitive: this.handleHotkeyToggleSensitive,
openMedia: this.handleHotkeyOpenMedia,
};
if (hidden) {

@ -67,6 +67,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>enter</kbd>, <kbd>o</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
</tr>
<tr>
<td><kbd>e</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.open_media' defaultMessage='to open media' /></td>
</tr>
<tr>
<td><kbd>x</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>

@ -320,6 +320,22 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('VIDEO', { media, time }));
}
handleHotkeyOpenMedia = e => {
const { status } = this.props;
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
this.handleOpenVideo(status.getIn(['media_attachments', 0]), 0);
} else {
this.handleOpenMedia(status.get('media_attachments'), 0);
}
}
}
handleMuteClick = (account) => {
this.props.dispatch(initMuteModal(account));
}
@ -529,6 +545,7 @@ class Status extends ImmutablePureComponent {
openProfile: this.handleHotkeyOpenProfile,
toggleSpoiler: this.handleExpandedToggle,
toggleSensitive: this.handleHotkeyToggleSensitive,
openMedia: this.handleHotkeyOpenMedia,
};
return (

@ -214,7 +214,7 @@ class FocalPointModal extends ImmutablePureComponent {
langPath: `${assetHost}/ocr/lang-data`,
});
let media_url = media.get('file');
let media_url = media.get('url');
if (window.URL && URL.createObjectURL) {
try {

@ -107,6 +107,7 @@ const keyMap = {
bookmark: 'd',
toggleCollapse: 'shift+x',
toggleSensitive: 'h',
openMedia: 'e',
};
class SwitchingColumnsArea extends React.PureComponent {

@ -488,7 +488,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
&nbsp;

@ -429,7 +429,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, fromJS(action.media), action.file);
case COMPOSE_UPLOAD_FAIL:
return state.set('is_uploading', false).update('pending_media_attachments', n => action.decrement ? n - 1 : n);
return state.set('is_uploading', false).update('pending_media_attachments', n => n - 1);
case COMPOSE_UPLOAD_UNDO:
return removeMedia(state, action.media_id);
case COMPOSE_UPLOAD_PROGRESS:

@ -652,7 +652,7 @@
}
.counter {
width: 33.3%;
min-width: 33.3%;
box-sizing: border-box;
flex: 0 0 auto;
color: $darker-text-color;

@ -236,7 +236,7 @@ export function uploadCompose(files) {
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
},
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).catch(error => dispatch(uploadComposeFail(error, true)));
}).catch(error => dispatch(uploadComposeFail(error)));
};
};
};
@ -267,11 +267,10 @@ export function changeUploadComposeSuccess(media) {
};
};
export function changeUploadComposeFail(error, decrement = false) {
export function changeUploadComposeFail(error) {
return {
type: COMPOSE_UPLOAD_CHANGE_FAIL,
error: error,
decrement: decrement,
skipLoading: true,
};
};

@ -214,6 +214,22 @@ class Status extends ImmutablePureComponent {
this.props.onOpenVideo(media, startTime);
}
handleHotkeyOpenMedia = e => {
const { status, onOpenMedia, onOpenVideo } = this.props;
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(status.getIn(['media_attachments', 0]), 0);
} else {
onOpenMedia(status.get('media_attachments'), 0);
}
}
}
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
@ -293,6 +309,7 @@ class Status extends ImmutablePureComponent {
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
openMedia: this.handleHotkeyOpenMedia,
};
if (hidden) {

@ -56,6 +56,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>enter</kbd>, <kbd>o</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
</tr>
<tr>
<td><kbd>e</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.open_media' defaultMessage='to open media' /></td>
</tr>
<tr>
<td><kbd>x</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>

@ -281,6 +281,22 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('VIDEO', { media, time }));
}
handleHotkeyOpenMedia = e => {
const { status } = this.props;
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
this.handleOpenVideo(status.getIn(['media_attachments', 0]), 0);
} else {
this.handleOpenMedia(status.get('media_attachments'), 0);
}
}
}
handleMuteClick = (account) => {
this.props.dispatch(initMuteModal(account));
}
@ -506,6 +522,7 @@ class Status extends ImmutablePureComponent {
openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
openMedia: this.handleHotkeyOpenMedia,
};
return (

@ -214,7 +214,7 @@ class FocalPointModal extends ImmutablePureComponent {
langPath: `${assetHost}/ocr/lang-data`,
});
let media_url = media.get('file');
let media_url = media.get('url');
if (window.URL && URL.createObjectURL) {
try {

@ -100,6 +100,7 @@ const keyMap = {
goToRequests: 'g r',
toggleHidden: 'x',
toggleSensitive: 'h',
openMedia: 'e',
};
class SwitchingColumnsArea extends React.PureComponent {

@ -467,7 +467,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>

@ -328,7 +328,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, fromJS(action.media), action.file);
case COMPOSE_UPLOAD_FAIL:
return state.set('is_uploading', false).update('pending_media_attachments', n => action.decrement ? n - 1 : n);
return state.set('is_uploading', false).update('pending_media_attachments', n => n - 1);
case COMPOSE_UPLOAD_UNDO:
return removeMedia(state, action.media_id);
case COMPOSE_UPLOAD_PROGRESS:

@ -646,7 +646,7 @@
}
.counter {
width: 33.3%;
min-width: 33.3%;
box-sizing: border-box;
flex: 0 0 auto;
color: $darker-text-color;

@ -1,7 +1,8 @@
# frozen_string_literal: true
class StatusRelationshipsPresenter
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
:bookmarks_map
def initialize(statuses, current_account_id = nil, **options)
if current_account_id.nil?

@ -97,8 +97,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def bookmarked
if instance_options && instance_options[:bookmarks]
instance_options[:bookmarks].bookmarks_map[object.id] || false
if instance_options && instance_options[:relationships]
instance_options[:relationships].bookmarks_map[object.id] || false
else
current_user.account.bookmarked?(object)
end

@ -30,7 +30,7 @@ class ActivityPub::ProcessPollService < BaseService
voters_count = @json['votersCount']
latest_options = items.map { |item| item['name'].presence || item['content'] }
latest_options = items.map { |item| item['name'].presence || item['content'] }.compact
# If for some reasons the options were changed, it invalidates all previous
# votes, so we need to remove them

@ -4,6 +4,10 @@
= simple_form_for current_user, url: settings_preferences_notifications_path, html: { method: :put } do |f|
= render 'shared/error_messages', object: current_user
%h4= t('notifications.email_events')
%p.hint = t('notifications.email_events_hint')
.fields-group
= f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
= ff.input :follow, as: :boolean, wrapper: :with_label
@ -21,6 +25,8 @@
= f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
= ff.input :digest, as: :boolean, wrapper: :with_label
%h4 = t('notifications.other_settings')
.fields-group
= f.simple_fields_for :interactions, hash_to_object(current_user.settings.interactions) do |ff|
= ff.input :must_be_follower, as: :boolean, wrapper: :with_label

@ -900,6 +900,10 @@ en:
body: 'Your status was boosted by %{name}:'
subject: "%{name} boosted your status"
title: New boost
notifications:
email_events: Events for e-mail notifications
email_events_hint: 'Select events that you want to receive notifications for:'
other_settings: Other notifications settings
number:
human:
decimal_units:

@ -163,14 +163,14 @@ en:
text: Why do you want to join?
notification_emails:
digest: Send digest e-mails
favourite: Send e-mail when someone favourites your status
follow: Send e-mail when someone follows you
follow_request: Send e-mail when someone requests to follow you
mention: Send e-mail when someone mentions you
pending_account: Send e-mail when a new account needs review
reblog: Send e-mail when someone boosts your status
report: Send e-mail when a new report is submitted
trending_tag: Send e-mail when an unreviewed hashtag is trending
favourite: Someone favourited your status
follow: Someone followed you
follow_request: Someone requested to follow you
mention: Someone mentioned you
pending_account: New account needs review
reblog: Someone boosted your status
report: New report is submitted
trending_tag: An unreviewed hashtag is trending
tag:
listable: Allow this hashtag to appear in searches and on the profile directory
name: Hashtag

@ -0,0 +1,41 @@
databases:
primary:
# Database URL (defaults to app database)
# url: <%= ENV["DATABASE_URL"] %>
# Add more databases
# other:
# url: <%= ENV["OTHER_DATABASE_URL"] %>
# Minimum time for long running queries
# long_running_query_sec: 60
# Minimum average time for slow queries
# slow_query_ms: 20
# Minimum calls for slow queries
# slow_query_calls: 100
# Minimum connections for high connections warning
# total_connections_threshold: 500
# Statement timeout for explain
# explain_timeout_sec: 10
# Time zone (defaults to app time zone)
# time_zone: "Pacific Time (US & Canada)"
# Basic authentication
# username: admin
# password: secret
# Stats database URL (defaults to app database)
# stats_database_url: <%= ENV["PGHERO_STATS_DATABASE_URL"] %>
# AWS configuration (defaults to app AWS config)
# also need aws_db_instance_identifier with each database
# aws_access_key_id: ...
# aws_secret_access_key: ...
# aws_region: us-east-1
override_csp: true

@ -64,14 +64,14 @@
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/plugin-proposal-decorators": "^7.7.0",
"@babel/plugin-proposal-object-rest-spread": "^7.6.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-react-inline-elements": "^7.2.0",
"@babel/plugin-transform-react-jsx-self": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-react-inline-elements": "^7.7.4",
"@babel/plugin-transform-react-jsx-self": "^7.7.4",
"@babel/plugin-transform-react-jsx-source": "^7.5.0",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.7.1",
"@babel/plugin-transform-runtime": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@babel/preset-react": "^7.7.0",
"@babel/runtime": "^7.7.2",
"@babel/runtime": "^7.7.4",
"@clusterws/cws": "^0.16.0",
"array-includes": "^3.0.3",
"atrament": "^0.2.3",
@ -161,7 +161,7 @@
"stringz": "^2.0.0",
"substring-trie": "^1.0.2",
"terser-webpack-plugin": "^2.2.1",
"tesseract.js": "^2.0.0-beta.2",
"tesseract.js": "^2.0.0-alpha.16",
"throng": "^4.0.0",
"tiny-queue": "^0.2.1",
"uuid": "^3.3.3",

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save