Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - README.md - app/javascript/styles/mastodon/components.scss conflicts caused by image URLs being different - app/models/status.rb as_home_timeline removed, kept glitch-soc-only as_direct_timeline - app/views/statuses/_simple_status.html.haml - config/locales/en.yml some strings were changed upstream - spec/models/status_spec.rb as_home_timeline removed, kept glitch-soc-only as_direct_timeline
This commit is contained in:
commit
61631f4751
62 changed files with 839 additions and 764 deletions
AUTHORS.mdCHANGELOG.mdGemfileGemfile.lock
app
controllers/api/v1
javascript
mastodon
actions
components
extended_video_player.jsgifv.jsmissing_indicator.jsregeneration_indicator.jsstatus_content.jsstatus_list.js
features
styles/mastodon
lib
models
services
views
about
admin
application
shared
statuses
config
db
lib
package.jsonspec
yarn.lock
668
AUTHORS.md
668
AUTHORS.md
File diff suppressed because it is too large
Load diff
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -3,6 +3,39 @@ Changelog
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.0.1] - 2019-10-10
|
||||
### Added
|
||||
|
||||
- Add `tootctl media usage` command ([Gargron](https://github.com/tootsuite/mastodon/pull/12115))
|
||||
- Add admin setting to auto-approve trending hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12122), [Gargron](https://github.com/tootsuite/mastodon/pull/12130))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change `tootctl media refresh` to skip already downloaded attachments ([Gargron](https://github.com/tootsuite/mastodon/pull/12118))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove auto-silence behaviour from spam check ([Gargron](https://github.com/tootsuite/mastodon/pull/12117))
|
||||
- Remove HTML `lang` attribute from individual statuses in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12124))
|
||||
- Remove fallback to long description on sidebar and meta description ([Gargron](https://github.com/tootsuite/mastodon/pull/12119))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix preloaded JSON-LD context for identity not being used ([Gargron](https://github.com/tootsuite/mastodon/pull/12138))
|
||||
- Fix media editing modal changing dimensions once the image loads ([Gargron](https://github.com/tootsuite/mastodon/pull/12131))
|
||||
- Fix not showing whether a custom emoji has a local counterpart in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12135))
|
||||
- Fix attachment not being re-downloaded even if file is not stored ([Gargron](https://github.com/tootsuite/mastodon/pull/12125))
|
||||
- Fix old migration trying to use new column due to default status scope ([Gargron](https://github.com/tootsuite/mastodon/pull/12095))
|
||||
- Fix column back button missing for not found accounts ([trwnh](https://github.com/tootsuite/mastodon/pull/12094))
|
||||
- Fix issues with tootctl's parallelization and progress reporting ([Gargron](https://github.com/tootsuite/mastodon/pull/12093), [Gargron](https://github.com/tootsuite/mastodon/pull/12097))
|
||||
- Fix existing user records with now-renamed `pt` locale ([Gargron](https://github.com/tootsuite/mastodon/pull/12092))
|
||||
- Fix hashtag timeline REST API accepting too many hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12091))
|
||||
- Fix `GET /api/v1/instance` REST APIs being unavailable in secure mode ([Gargron](https://github.com/tootsuite/mastodon/pull/12089))
|
||||
- Fix performance of home feed regeneration and merging ([Gargron](https://github.com/tootsuite/mastodon/pull/12084))
|
||||
- Fix ffmpeg performance issues due to stdout buffer overflow ([hugogameiro](https://github.com/tootsuite/mastodon/pull/12088))
|
||||
- Fix S3 adapter retrying failing uploads with exponential backoff ([Gargron](https://github.com/tootsuite/mastodon/pull/12085))
|
||||
- Fix `tootctl accounts cull` advertising unused option flag ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12074))
|
||||
|
||||
## [3.0.0] - 2019-10-03
|
||||
### Added
|
||||
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -90,7 +90,7 @@ gem 'simple_form', '~> 4.1'
|
|||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 2.1.3'
|
||||
gem 'strong_migrations', '~> 0.4'
|
||||
gem 'tty-command', '~> 0.8', require: false
|
||||
gem 'tty-command', '~> 0.9', require: false
|
||||
gem 'tty-prompt', '~> 0.19', require: false
|
||||
gem 'twitter-text', '~> 1.14'
|
||||
gem 'tzinfo-data', '~> 1.2019'
|
||||
|
@ -119,7 +119,7 @@ end
|
|||
group :test do
|
||||
gem 'capybara', '~> 3.29'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.4'
|
||||
gem 'faker', '~> 2.5'
|
||||
gem 'microformats', '~> 4.1'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.0'
|
||||
|
|
33
Gemfile.lock
33
Gemfile.lock
|
@ -93,7 +93,7 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
airbrussh (1.3.3)
|
||||
airbrussh (1.3.4)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
annotate (2.7.5)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
|
@ -142,7 +142,7 @@ GEM
|
|||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
byebug (11.0.0)
|
||||
capistrano (3.11.1)
|
||||
capistrano (3.11.2)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
|
@ -188,13 +188,14 @@ GEM
|
|||
css_parser (1.7.0)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
derailed_benchmarks (1.3.6)
|
||||
derailed_benchmarks (1.4.0)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
heapy (~> 0)
|
||||
memory_profiler (~> 0)
|
||||
rack (>= 1)
|
||||
rake (> 10, < 13)
|
||||
ruby-statistics (>= 2.1)
|
||||
thor (~> 0.19)
|
||||
devise (4.7.1)
|
||||
bcrypt (~> 3.0)
|
||||
|
@ -233,13 +234,13 @@ GEM
|
|||
faraday
|
||||
multi_json
|
||||
encryptor (3.0.0)
|
||||
equatable (0.5.0)
|
||||
equatable (0.6.1)
|
||||
erubi (1.8.0)
|
||||
et-orbi (1.1.6)
|
||||
tzinfo
|
||||
excon (0.62.0)
|
||||
fabrication (2.20.2)
|
||||
faker (2.4.0)
|
||||
faker (2.5.0)
|
||||
i18n (~> 1.6.0)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
|
@ -265,7 +266,8 @@ GEM
|
|||
fuubar (2.4.1)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
get_process_mem (0.2.3)
|
||||
get_process_mem (0.2.4)
|
||||
ffi (~> 1.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
goldfinger (2.1.0)
|
||||
|
@ -429,13 +431,13 @@ GEM
|
|||
parser (2.6.4.0)
|
||||
ast (~> 2.4.0)
|
||||
parslet (1.8.2)
|
||||
pastel (0.7.2)
|
||||
equatable (~> 0.5.0)
|
||||
tty-color (~> 0.4.0)
|
||||
pastel (0.7.3)
|
||||
equatable (~> 0.6)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.1.4)
|
||||
pghero (2.3.0)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.3.8)
|
||||
pkg-config (1.3.9)
|
||||
premailer (1.11.1)
|
||||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
|
@ -571,6 +573,7 @@ GEM
|
|||
ruby-progressbar (1.10.1)
|
||||
ruby-saml (1.9.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
ruby-statistics (2.1.1)
|
||||
rufus-scheduler (3.5.2)
|
||||
fugit (~> 1.1, >= 1.1.5)
|
||||
safe_yaml (1.0.5)
|
||||
|
@ -629,8 +632,8 @@ GEM
|
|||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
tty-color (0.4.3)
|
||||
tty-command (0.8.2)
|
||||
tty-color (0.5.0)
|
||||
tty-command (0.9.0)
|
||||
pastel (~> 0.7.0)
|
||||
tty-cursor (0.7.0)
|
||||
tty-prompt (0.19.0)
|
||||
|
@ -655,7 +658,7 @@ GEM
|
|||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webmock (3.7.5)
|
||||
webmock (3.7.6)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
@ -709,7 +712,7 @@ DEPENDENCIES
|
|||
doorkeeper (~> 5.2)
|
||||
dotenv-rails (~> 2.7)
|
||||
fabrication (~> 2.20)
|
||||
faker (~> 2.4)
|
||||
faker (~> 2.5)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
|
@ -796,7 +799,7 @@ DEPENDENCIES
|
|||
streamio-ffmpeg (~> 3.0)
|
||||
strong_migrations (~> 0.4)
|
||||
thor (~> 0.20)
|
||||
tty-command (~> 0.8)
|
||||
tty-command (~> 0.9)
|
||||
tty-prompt (~> 0.19)
|
||||
twitter-text (~> 1.14)
|
||||
tzinfo-data (~> 1.2019)
|
||||
|
|
|
@ -4,6 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
|||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
|
|||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ class Api::V1::InstancesController < Api::BaseController
|
|||
respond_to :json
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
|
|
|
@ -5,11 +5,17 @@ class Api::V1::StreamingController < Api::BaseController
|
|||
|
||||
def index
|
||||
if Rails.configuration.x.streaming_api_base_url != request.host
|
||||
uri = URI.parse(request.url)
|
||||
uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
||||
redirect_to uri.to_s, status: 301
|
||||
redirect_to streaming_api_url, status: 301
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def streaming_api_url
|
||||
Addressable::URI.parse(request.url).tap do |uri|
|
||||
uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
||||
end.to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
|||
render json: @statuses,
|
||||
each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
status: regeneration_in_progress? ? 206 : 200
|
||||
status: account_home_feed.regenerating? ? 206 : 200
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -62,8 +62,4 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
|||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
|
||||
def regeneration_in_progress?
|
||||
Redis.current.exists("account:#{current_account.id}:regeneration")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -97,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
|||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
|
||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
|
||||
done();
|
||||
}).catch(error => {
|
||||
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ExtendedVideoPlayer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
time: PropTypes.number,
|
||||
controls: PropTypes.bool.isRequired,
|
||||
muted: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
handleLoadedData = () => {
|
||||
if (this.props.time) {
|
||||
this.video.currentTime = this.props.time;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.video = c;
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
e.stopPropagation();
|
||||
const handler = this.props.onClick;
|
||||
if (handler) handler();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { src, muted, controls, alt } = this.props;
|
||||
|
||||
return (
|
||||
<div className='extended-video-player'>
|
||||
<video
|
||||
ref={this.setRef}
|
||||
src={src}
|
||||
autoPlay
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
muted={muted}
|
||||
controls={controls}
|
||||
loop={!controls}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
75
app/javascript/mastodon/components/gifv.js
Normal file
75
app/javascript/mastodon/components/gifv.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class GIFV extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
loading: true,
|
||||
};
|
||||
|
||||
handleLoadedData = () => {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.src !== this.props.src) {
|
||||
this.setState({ loading: true });
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const { onClick } = this.props;
|
||||
|
||||
if (onClick) {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { src, width, height, alt } = this.props;
|
||||
const { loading } = this.state;
|
||||
|
||||
return (
|
||||
<div className='gifv' style={{ position: 'relative' }}>
|
||||
{loading && (
|
||||
<canvas
|
||||
width={width}
|
||||
height={height}
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<video
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
muted
|
||||
loop
|
||||
autoPlay
|
||||
playsInline
|
||||
onClick={this.handleClick}
|
||||
onLoadedData={this.handleLoadedData}
|
||||
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
<div className='regeneration-indicator missing-indicator'>
|
||||
<div>
|
||||
<div className='regeneration-indicator__figure' />
|
||||
const MissingIndicator = ({ fullPage }) => (
|
||||
<div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
</div>
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
MissingIndicator.propTypes = {
|
||||
fullPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default MissingIndicator;
|
||||
|
|
18
app/javascript/mastodon/components/regeneration_indicator.js
Normal file
18
app/javascript/mastodon/components/regeneration_indicator.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'mastodon/../images/elephant_ui_working.svg';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
<div className='regeneration-indicator'>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
|
||||
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MissingIndicator;
|
|
@ -216,14 +216,14 @@ export default class StatusContent extends React.PureComponent {
|
|||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
|
||||
<span dangerouslySetInnerHTML={spoilerContent} />
|
||||
{' '}
|
||||
<button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
|
||||
</p>
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
</div>
|
||||
|
@ -231,7 +231,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
} else if (this.props.onClick) {
|
||||
const output = [
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
</div>,
|
||||
|
@ -245,7 +245,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
} else {
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
|
||||
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { debounce } from 'lodash';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusContainer from '../containers/status_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import LoadGap from './load_gap';
|
||||
import ScrollableList from './scrollable_list';
|
||||
import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
|
@ -81,18 +81,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
const { isLoading, isPartial } = other;
|
||||
|
||||
if (isPartial) {
|
||||
return (
|
||||
<div className='regeneration-indicator'>
|
||||
<div>
|
||||
<div className='regeneration-indicator__figure' />
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
|
||||
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <RegenerationIndicator />;
|
||||
}
|
||||
|
||||
let scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
|
|
|
@ -83,6 +83,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import MissingIndicator from '../../components/missing_indicator';
|
|||
|
||||
const GenericNotFound = () => (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
<MissingIndicator fullPage />
|
||||
</Column>
|
||||
);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import UploadProgress from 'mastodon/features/compose/components/upload_progress
|
|||
import CharacterCounter from 'mastodon/features/compose/components/character_counter';
|
||||
import { length } from 'stringz';
|
||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
|
||||
import GIFV from 'mastodon/components/gifv';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -41,6 +42,36 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
|
|||
|
||||
const assetHost = process.env.CDN_HOST || '';
|
||||
|
||||
class ImageLoader extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
};
|
||||
|
||||
state = {
|
||||
loading: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const image = new Image();
|
||||
image.addEventListener('load', () => this.setState({ loading: false }));
|
||||
image.src = this.props.src;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { loading } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return <canvas width={this.props.width} height={this.props.height} />;
|
||||
} else {
|
||||
return <img {...this.props} alt='' />;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class FocalPointModal extends ImmutablePureComponent {
|
||||
|
@ -60,6 +91,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
description: '',
|
||||
dirty: false,
|
||||
progress: 0,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -242,8 +274,8 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
<div className='focal-point-modal__content'>
|
||||
{focals && (
|
||||
<div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
|
||||
{media.get('type') === 'image' && <img src={media.get('url')} width={width} height={height} alt='' />}
|
||||
{media.get('type') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />}
|
||||
{media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
|
||||
{media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
|
||||
|
||||
<div className='focal-point__preview'>
|
||||
<strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
|
||||
|
|
|
@ -3,13 +3,13 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Video from 'mastodon/features/video';
|
||||
import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
|
||||
import classNames from 'classnames';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImageLoader from './image_loader';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import GIFV from 'mastodon/components/gifv';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -169,10 +169,8 @@ class MediaModal extends ImmutablePureComponent {
|
|||
);
|
||||
} else if (image.get('type') === 'gifv') {
|
||||
return (
|
||||
<ExtendedVideoPlayer
|
||||
<GIFV
|
||||
src={image.get('url')}
|
||||
muted
|
||||
controls={false}
|
||||
width={width}
|
||||
height={height}
|
||||
key={image.get('preview_url')}
|
||||
|
|
|
@ -3127,37 +3127,27 @@ a.status-card.compact:hover {
|
|||
cursor: default;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&__figure {
|
||||
background: url('~images/elephant_ui_working.svg') no-repeat center 0;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background-size: contain;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&.missing-indicator {
|
||||
padding-top: 20px + 48px;
|
||||
|
||||
.regeneration-indicator__figure {
|
||||
background-image: url('~images/elephant_ui_disappointed.svg');
|
||||
&,
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 160px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--without-header {
|
||||
padding-top: 20px + 48px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-top: 200px;
|
||||
margin-top: 30px;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
|
@ -6102,7 +6092,8 @@ noscript {
|
|||
background: $base-shadow-color;
|
||||
|
||||
img,
|
||||
video {
|
||||
video,
|
||||
canvas {
|
||||
display: block;
|
||||
max-height: 80vh;
|
||||
width: 100%;
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: $ui-base-color;
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
background: darken($ui-base-color, 8%);
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class FeedManager
|
|||
|
||||
def filter?(timeline_type, status, receiver_id)
|
||||
if timeline_type == :home
|
||||
filter_from_home?(status, receiver_id)
|
||||
filter_from_home?(status, receiver_id, build_crutches(receiver_id, [status]))
|
||||
elsif timeline_type == :mentions
|
||||
filter_from_mentions?(status, receiver_id)
|
||||
elsif timeline_type == :direct
|
||||
|
@ -31,6 +31,7 @@ class FeedManager
|
|||
|
||||
def push_to_home(account, status)
|
||||
return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||
|
||||
trim(:home, account.id)
|
||||
PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
|
||||
true
|
||||
|
@ -38,6 +39,7 @@ class FeedManager
|
|||
|
||||
def unpush_from_home(account, status)
|
||||
return false unless remove_from_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||
|
||||
redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
|
||||
true
|
||||
end
|
||||
|
@ -49,7 +51,9 @@ class FeedManager
|
|||
should_filter &&= !(list.show_list_replies? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
|
||||
return false if should_filter
|
||||
end
|
||||
|
||||
return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
|
||||
|
||||
trim(:list, list.id)
|
||||
PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
|
||||
true
|
||||
|
@ -57,6 +61,7 @@ class FeedManager
|
|||
|
||||
def unpush_from_list(list, status)
|
||||
return false unless remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
|
||||
|
||||
redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
|
||||
true
|
||||
end
|
||||
|
@ -100,16 +105,21 @@ class FeedManager
|
|||
|
||||
def merge_into_timeline(from_account, into_account)
|
||||
timeline_key = key(:home, into_account.id)
|
||||
query = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
|
||||
aggregate = into_account.user&.aggregates_reblogs?
|
||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||
|
||||
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
|
||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||
query = query.where('id > ?', oldest_home_score)
|
||||
end
|
||||
|
||||
query.each do |status|
|
||||
next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
|
||||
add_to_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?)
|
||||
statuses = query.to_a
|
||||
crutches = build_crutches(into_account.id, statuses)
|
||||
|
||||
statuses.each do |status|
|
||||
next if filter_from_home?(status, into_account, crutches)
|
||||
|
||||
add_to_feed(:home, into_account.id, status, aggregate)
|
||||
end
|
||||
|
||||
trim(:home, into_account.id)
|
||||
|
@ -135,24 +145,35 @@ class FeedManager
|
|||
end
|
||||
|
||||
def populate_feed(account)
|
||||
added = 0
|
||||
limit = FeedManager::MAX_ITEMS / 2
|
||||
max_id = nil
|
||||
limit = FeedManager::MAX_ITEMS / 2
|
||||
aggregate = account.user&.aggregates_reblogs?
|
||||
timeline_key = key(:home, account.id)
|
||||
|
||||
loop do
|
||||
statuses = Status.as_home_timeline(account)
|
||||
.paginate_by_max_id(limit, max_id)
|
||||
account.statuses.where.not(visibility: :direct).limit(limit).each do |status|
|
||||
add_to_feed(:home, account.id, status, aggregate)
|
||||
end
|
||||
|
||||
break if statuses.empty?
|
||||
account.following.includes(:account_stat).find_each do |target_account|
|
||||
if redis.zcard(timeline_key) >= limit
|
||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||
last_status_score = Mastodon::Snowflake.id_at(account.last_status_at)
|
||||
|
||||
statuses.each do |status|
|
||||
next if filter_from_home?(status, account)
|
||||
added += 1 if add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||
# If the feed is full and this account has not posted more recently
|
||||
# than the last item on the feed, then we can skip the whole account
|
||||
# because none of its statuses would stay on the feed anyway
|
||||
next if last_status_score < oldest_home_score
|
||||
end
|
||||
|
||||
break unless added.zero?
|
||||
statuses = target_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(limit)
|
||||
crutches = build_crutches(account.id, statuses)
|
||||
|
||||
max_id = statuses.last.id
|
||||
statuses.each do |status|
|
||||
next if filter_from_home?(status, account, crutches)
|
||||
|
||||
add_to_feed(:home, account.id, status, aggregate)
|
||||
end
|
||||
|
||||
trim(:home, account.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -188,31 +209,33 @@ class FeedManager
|
|||
(context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?)
|
||||
end
|
||||
|
||||
def filter_from_home?(status, receiver_id)
|
||||
def filter_from_home?(status, receiver_id, crutches)
|
||||
return false if receiver_id == status.account_id
|
||||
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
|
||||
return true if phrase_filtered?(status, receiver_id, :home)
|
||||
|
||||
check_for_blocks = status.active_mentions.pluck(:account_id)
|
||||
check_for_blocks = crutches[:active_mentions][status.id] || []
|
||||
check_for_blocks.concat([status.account_id])
|
||||
|
||||
if status.reblog?
|
||||
check_for_blocks.concat([status.reblog.account_id])
|
||||
check_for_blocks.concat(status.reblog.active_mentions.pluck(:account_id))
|
||||
check_for_blocks.concat(crutches[:active_mentions][status.reblog_of_id] || [])
|
||||
end
|
||||
|
||||
return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home)
|
||||
return true if check_for_blocks.any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] }
|
||||
|
||||
if status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
|
||||
should_filter = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists? # and I'm not following the person it's a reply to
|
||||
should_filter = !crutches[:following][status.in_reply_to_account_id] # and I'm not following the person it's a reply to
|
||||
should_filter &&= receiver_id != status.in_reply_to_account_id # and it's not a reply to me
|
||||
should_filter &&= status.account_id != status.in_reply_to_account_id # and it's not a self-reply
|
||||
return should_filter
|
||||
|
||||
return !!should_filter
|
||||
elsif status.reblog? # Filter out a reblog
|
||||
should_filter = Follow.where(account_id: receiver_id, target_account_id: status.account_id, show_reblogs: false).exists? # if the reblogger's reblogs are suppressed
|
||||
should_filter ||= Block.where(account_id: status.reblog.account_id, target_account_id: receiver_id).exists? # or if the author of the reblogged status is blocking me
|
||||
should_filter ||= AccountDomainBlock.where(account_id: receiver_id, domain: status.reblog.account.domain).exists? # or the author's domain is blocked
|
||||
return should_filter
|
||||
should_filter = crutches[:hiding_reblogs][status.account_id] # if the reblogger's reblogs are suppressed
|
||||
should_filter ||= crutches[:blocked_by][status.reblog.account_id] # or if the author of the reblogged status is blocking me
|
||||
should_filter ||= crutches[:domain_blocking][status.reblog.account.domain] # or the author's domain is blocked
|
||||
|
||||
return !!should_filter
|
||||
end
|
||||
|
||||
false
|
||||
|
@ -349,4 +372,31 @@ class FeedManager
|
|||
|
||||
redis.zrem(timeline_key, status.id)
|
||||
end
|
||||
|
||||
def build_crutches(receiver_id, statuses)
|
||||
crutches = {}
|
||||
|
||||
crutches[:active_mentions] = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact).pluck(:status_id, :account_id).each_with_object({}) { |(id, account_id), mapping| (mapping[id] ||= []).push(account_id) }
|
||||
|
||||
check_for_blocks = statuses.flat_map do |s|
|
||||
arr = crutches[:active_mentions][s.id] || []
|
||||
arr.concat([s.account_id])
|
||||
|
||||
if s.reblog?
|
||||
arr.concat([s.reblog.account_id])
|
||||
arr.concat(crutches[:active_mentions][s.reblog_of_id] || [])
|
||||
end
|
||||
|
||||
arr
|
||||
end
|
||||
|
||||
crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
|
||||
crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
|
||||
crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
|
||||
crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
|
||||
crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
|
||||
crutches[:blocked_by] = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
|
||||
|
||||
crutches
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,6 @@ class SpamCheck
|
|||
end
|
||||
|
||||
def flag!
|
||||
auto_silence_account!
|
||||
auto_report_status!
|
||||
end
|
||||
|
||||
|
@ -134,17 +133,13 @@ class SpamCheck
|
|||
text.gsub(/\s+/, ' ').strip
|
||||
end
|
||||
|
||||
def auto_silence_account!
|
||||
@account.silence!
|
||||
end
|
||||
|
||||
def auto_report_status!
|
||||
status_ids = Status.where(visibility: %i(public unlisted)).where(id: matching_status_ids).pluck(:id) + [@status.id] if @status.distributable?
|
||||
ReportService.new.call(Account.representative, @account, status_ids: status_ids, comment: I18n.t('spam_check.spam_detected_and_silenced'))
|
||||
end
|
||||
|
||||
def already_flagged?
|
||||
@account.silenced?
|
||||
@account.silenced? || @account.targeted_reports.unresolved.where(account_id: -99).exists?
|
||||
end
|
||||
|
||||
def trusted?
|
||||
|
|
|
@ -202,7 +202,7 @@ class Account < ApplicationRecord
|
|||
end
|
||||
|
||||
def unsilence!
|
||||
update!(silenced_at: nil, trust_level: trust_level == TRUST_LEVELS[:untrusted] ? TRUST_LEVELS[:trusted] : trust_level)
|
||||
update!(silenced_at: nil)
|
||||
end
|
||||
|
||||
def suspended?
|
||||
|
@ -312,10 +312,9 @@ class Account < ApplicationRecord
|
|||
def save_with_optional_media!
|
||||
save!
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
self.avatar = nil
|
||||
self.header = nil
|
||||
self[:avatar_remote_url] = ''
|
||||
self[:header_remote_url] = ''
|
||||
self.avatar = nil
|
||||
self.header = nil
|
||||
|
||||
save!
|
||||
end
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ class Admin::AccountAction
|
|||
|
||||
def process_action!
|
||||
case type
|
||||
when 'none'
|
||||
handle_resolve!
|
||||
when 'disable'
|
||||
handle_disable!
|
||||
when 'silence'
|
||||
|
@ -103,6 +105,16 @@ class Admin::AccountAction
|
|||
end
|
||||
end
|
||||
|
||||
def handle_resolve!
|
||||
if with_report? && report.account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
|
||||
# This is an automated report and it is being dismissed, so it's
|
||||
# a false positive, in which case update the account's trust level
|
||||
# to prevent further spam checks
|
||||
|
||||
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
|
||||
end
|
||||
end
|
||||
|
||||
def handle_disable!
|
||||
authorize(target_account.user, :disable?)
|
||||
log_action(:disable, target_account.user)
|
||||
|
|
|
@ -18,7 +18,7 @@ module Remotable
|
|||
return
|
||||
end
|
||||
|
||||
return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || self[attribute_name] == url
|
||||
return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || (self[attribute_name] == url && send("#{attachment_name}_file_name").present?)
|
||||
|
||||
begin
|
||||
Request.new(:get, url).perform do |response|
|
||||
|
|
|
@ -36,6 +36,7 @@ class Form::AdminSettings
|
|||
show_replies_in_public_timelines
|
||||
spam_check_enabled
|
||||
trends
|
||||
trendable_by_default
|
||||
show_domain_blocks
|
||||
show_domain_blocks_rationale
|
||||
noindex
|
||||
|
@ -56,6 +57,7 @@ class Form::AdminSettings
|
|||
show_replies_in_public_timelines
|
||||
spam_check_enabled
|
||||
trends
|
||||
trendable_by_default
|
||||
noindex
|
||||
).freeze
|
||||
|
||||
|
|
|
@ -7,19 +7,7 @@ class HomeFeed < Feed
|
|||
@account = account
|
||||
end
|
||||
|
||||
def get(limit, max_id = nil, since_id = nil, min_id = nil)
|
||||
if redis.exists("account:#{@account.id}:regeneration")
|
||||
from_database(limit, max_id, since_id, min_id)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def from_database(limit, max_id, since_id, min_id)
|
||||
Status.as_home_timeline(@account)
|
||||
.paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||
.reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
|
||||
def regenerating?
|
||||
redis.exists("account:#{@id}:regeneration")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,7 @@ class MediaAttachment < ApplicationRecord
|
|||
small: {
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
|
||||
},
|
||||
},
|
||||
|
@ -70,6 +71,7 @@ class MediaAttachment < ApplicationRecord
|
|||
keep_same_format: true,
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
'map_metadata' => '-1',
|
||||
'c:v' => 'copy',
|
||||
'c:a' => 'copy',
|
||||
|
@ -84,6 +86,7 @@ class MediaAttachment < ApplicationRecord
|
|||
content_type: 'audio/mpeg',
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
'q:a' => 2,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -291,10 +291,6 @@ class Status < ApplicationRecord
|
|||
where(language: nil).or where(language: account.chosen_languages)
|
||||
end
|
||||
|
||||
def as_home_timeline(account)
|
||||
where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
|
||||
end
|
||||
|
||||
def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
|
||||
# direct timeline is mix of direct message from_me and to_me.
|
||||
# 2 queries are executed with pagination.
|
||||
|
|
|
@ -37,6 +37,7 @@ class Tag < ApplicationRecord
|
|||
scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
|
||||
scope :usable, -> { where(usable: [true, nil]) }
|
||||
scope :listable, -> { where(listable: [true, nil]) }
|
||||
scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) }
|
||||
scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
|
||||
scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
|
||||
scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
|
||||
|
@ -76,7 +77,7 @@ class Tag < ApplicationRecord
|
|||
alias listable? listable
|
||||
|
||||
def trendable
|
||||
boolean_with_default('trendable', false)
|
||||
boolean_with_default('trendable', Setting.trendable_by_default)
|
||||
end
|
||||
|
||||
alias trendable? trendable
|
||||
|
|
|
@ -90,7 +90,7 @@ class TrendingTags
|
|||
tag_ids = redis.zrevrange(KEY, 0, LIMIT - 1).map(&:to_i)
|
||||
|
||||
tags = Tag.where(id: tag_ids)
|
||||
tags = tags.where(trendable: true) if filtered
|
||||
tags = tags.trendable if filtered
|
||||
tags = tags.each_with_object({}) { |tag, h| h[tag.id] = tag }
|
||||
|
||||
tag_ids.map { |tag_id| tags[tag_id] }.compact.take(limit)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class HashtagQueryService < BaseService
|
||||
LIMIT_PER_MODE = 4
|
||||
|
||||
def call(tag, params, account = nil, local = false)
|
||||
tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
|
||||
all = tags_for(params[:all])
|
||||
|
@ -15,6 +17,6 @@ class HashtagQueryService < BaseService
|
|||
private
|
||||
|
||||
def tags_for(names)
|
||||
Tag.matching_name(names) if names.presence
|
||||
Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,13 +52,12 @@
|
|||
.hero-widget__img
|
||||
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
|
||||
|
||||
- if @instance_presenter.site_short_description.present?
|
||||
.hero-widget__text
|
||||
%p
|
||||
= @instance_presenter.site_short_description.html_safe.presence
|
||||
= link_to about_more_path do
|
||||
= t('about.learn_more')
|
||||
= fa_icon 'angle-double-right'
|
||||
.hero-widget__text
|
||||
%p
|
||||
= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
|
||||
= link_to about_more_path do
|
||||
= t('about.learn_more')
|
||||
= fa_icon 'angle-double-right'
|
||||
|
||||
.hero-widget__footer
|
||||
.hero-widget__footer__column
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
- else
|
||||
= custom_emoji.domain
|
||||
|
||||
- if custom_emoji.local_counterpart.present?
|
||||
•
|
||||
= t('admin.accounts.location.local')
|
||||
|
||||
%br/
|
||||
|
||||
- if custom_emoji.disabled?
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
= f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
|
||||
|
||||
.fields-group
|
||||
= f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 4 }
|
||||
= f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
|
||||
|
||||
.fields-group
|
||||
= f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
|
||||
= f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6.fields-group
|
||||
|
@ -71,6 +71,9 @@
|
|||
.fields-group
|
||||
= f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
|
||||
|
||||
|
@ -101,8 +104,8 @@
|
|||
= f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
|
||||
|
||||
.fields-group
|
||||
= f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
|
||||
= f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
|
||||
= f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
|
||||
= f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
|
||||
= f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
|
||||
|
||||
.hero-widget__text
|
||||
%p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
|
||||
%p= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
|
||||
|
||||
- if Setting.trends && !(user_signed_in? && !current_user.setting_trends)
|
||||
- trends = TrendingTags.get(3)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- thumbnail = @instance_presenter.thumbnail
|
||||
- description ||= strip_tags(@instance_presenter.site_short_description.presence || @instance_presenter.site_description.presence || t('about.about_mastodon_html'))
|
||||
- description ||= strip_tags(@instance_presenter.site_short_description.presence || t('about.about_mastodon_html'))
|
||||
|
||||
%meta{ name: 'description', content: description }/
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
%p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
|
||||
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}
|
||||
%button.status__content__spoiler-link= t('statuses.show_more')
|
||||
.e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
|
||||
.e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
|
||||
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
|
||||
- if status.preloadable_poll
|
||||
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
%p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
|
||||
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}
|
||||
%button.status__content__spoiler-link= t('statuses.show_more')
|
||||
.e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
|
||||
.e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
|
||||
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
|
||||
- if status.preloadable_poll
|
||||
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
lock '3.11.1'
|
||||
lock '3.11.2'
|
||||
|
||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
||||
set :branch, ENV.fetch('BRANCH', 'master')
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../lib/json_ld/security'
|
||||
require_relative '../../lib/json_ld/identity'
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Paperclip.options[:read_timeout] = 60
|
||||
|
||||
Paperclip.interpolates :filename do |attachment, style|
|
||||
return attachment.original_filename if style == :original
|
||||
[basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
|
||||
if style == :original
|
||||
attachment.original_filename
|
||||
else
|
||||
[basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
|
||||
end
|
||||
end
|
||||
|
||||
Paperclip::Attachment.default_options.merge!(
|
||||
|
@ -24,22 +25,27 @@ if ENV['S3_ENABLED'] == 'true'
|
|||
storage: :s3,
|
||||
s3_protocol: s3_protocol,
|
||||
s3_host_name: s3_hostname,
|
||||
|
||||
s3_headers: {
|
||||
'X-Amz-Multipart-Threshold' => ENV.fetch('S3_MULTIPART_THRESHOLD') { 15.megabytes }.to_i,
|
||||
'Cache-Control' => 'public, max-age=315576000, immutable',
|
||||
},
|
||||
|
||||
s3_permissions: ENV.fetch('S3_PERMISSION') { 'public-read' },
|
||||
s3_region: s3_region,
|
||||
|
||||
s3_credentials: {
|
||||
bucket: ENV['S3_BUCKET'],
|
||||
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
|
||||
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
|
||||
},
|
||||
|
||||
s3_options: {
|
||||
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
|
||||
http_open_timeout: 5,
|
||||
http_read_timeout: 5,
|
||||
http_idle_timeout: 5,
|
||||
retry_limit: 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -48,6 +54,7 @@ if ENV['S3_ENABLED'] == 'true'
|
|||
endpoint: ENV['S3_ENDPOINT'],
|
||||
force_path_style: true
|
||||
)
|
||||
|
||||
Paperclip::Attachment.default_options[:url] = ':s3_path_url'
|
||||
end
|
||||
|
||||
|
@ -73,6 +80,7 @@ elsif ENV['SWIFT_ENABLED'] == 'true'
|
|||
openstack_region: ENV['SWIFT_REGION'],
|
||||
openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
|
||||
},
|
||||
|
||||
fog_directory: ENV['SWIFT_CONTAINER'],
|
||||
fog_host: ENV['SWIFT_OBJECT_URL'],
|
||||
fog_public: true
|
||||
|
@ -81,7 +89,7 @@ else
|
|||
Paperclip::Attachment.default_options.merge!(
|
||||
storage: :filesystem,
|
||||
use_timestamp: true,
|
||||
path: (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
|
||||
url: (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename',
|
||||
path: ENV.fetch('PAPERCLIP_ROOT_PATH', ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
|
||||
url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:class/:attachment/:id_partition/:style/:filename',
|
||||
)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
en:
|
||||
about:
|
||||
about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
|
||||
about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
|
||||
about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!'
|
||||
about_this: About
|
||||
active_count_after: active
|
||||
active_footnote: Monthly Active Users (MAU)
|
||||
|
@ -18,7 +18,6 @@ en:
|
|||
discover_users: Discover users
|
||||
documentation: Documentation
|
||||
federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
|
||||
generic_description: "%{domain} is one server in the network"
|
||||
get_apps: Try a mobile app
|
||||
hosted_on: Mastodon hosted on %{domain}
|
||||
instance_actor_flash: |
|
||||
|
@ -486,8 +485,8 @@ en:
|
|||
open: Anyone can sign up
|
||||
title: Registrations mode
|
||||
show_known_fediverse_at_about_page:
|
||||
desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
|
||||
title: Show known fediverse on timeline preview
|
||||
desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content
|
||||
title: Include federated content on unauthenticated public timeline page
|
||||
show_reblogs_in_public_timelines:
|
||||
desc_html: Show public boosts of public toots in local and public timelines.
|
||||
title: Show boosts in public timelines
|
||||
|
@ -511,15 +510,18 @@ en:
|
|||
title: Custom terms of service
|
||||
site_title: Server name
|
||||
spam_check_enabled:
|
||||
desc_html: Mastodon can auto-silence and auto-report accounts that send repeated unsolicited messages. There may be false positives.
|
||||
desc_html: Mastodon can auto-report accounts that send repeated unsolicited messages. There may be false positives.
|
||||
title: Anti-spam automation
|
||||
thumbnail:
|
||||
desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
|
||||
title: Server thumbnail
|
||||
timeline_preview:
|
||||
desc_html: Display public timeline on landing page
|
||||
title: Timeline preview
|
||||
desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication
|
||||
title: Allow unauthenticated access to public timeline
|
||||
title: Site settings
|
||||
trendable_by_default:
|
||||
desc_html: Affects hashtags that have not been previously disallowed
|
||||
title: Allow hashtags to trend without prior review
|
||||
trends:
|
||||
desc_html: Publicly display previously reviewed hashtags that are currently trending
|
||||
title: Trending hashtags
|
||||
|
|
|
@ -40,6 +40,7 @@ defaults: &defaults
|
|||
use_blurhash: true
|
||||
use_pending_items: false
|
||||
trends: true
|
||||
trendable_by_default: false
|
||||
notification_emails:
|
||||
follow: false
|
||||
reblog: false
|
||||
|
|
|
@ -52,6 +52,6 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
|
|||
end
|
||||
|
||||
def notifications_about_direct_statuses
|
||||
Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct })
|
||||
Notification.joins('INNER JOIN mentions ON mentions.id = notifications.activity_id INNER JOIN statuses ON statuses.id = mentions.status_id').where(activity_type: 'Mention', statuses: { visibility: :direct })
|
||||
end
|
||||
end
|
||||
|
|
11
db/migrate/20191007013357_update_pt_locales.rb
Normal file
11
db/migrate/20191007013357_update_pt_locales.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class UpdatePtLocales < ActiveRecord::Migration[5.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
User.where(locale: 'pt').in_batches.update_all(locale: 'pt-PT')
|
||||
end
|
||||
|
||||
def down
|
||||
User.where(locale: 'pt-PT').in_batches.update_all(locale: 'pt')
|
||||
end
|
||||
end
|
26
db/schema.rb
26
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: 2019_10_01_213028) do
|
||||
ActiveRecord::Schema.define(version: 2019_10_07_013357) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -706,6 +706,30 @@ ActiveRecord::Schema.define(version: 2019_10_01_213028) do
|
|||
t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
|
||||
end
|
||||
|
||||
create_table "stream_entries", force: :cascade do |t|
|
||||
t.bigint "activity_id"
|
||||
t.string "activity_type"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "hidden", default: false, null: false
|
||||
t.bigint "account_id"
|
||||
t.index ["account_id", "activity_type", "id"], name: "index_stream_entries_on_account_id_and_activity_type_and_id"
|
||||
t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type"
|
||||
end
|
||||
|
||||
create_table "subscriptions", force: :cascade do |t|
|
||||
t.string "callback_url", default: "", null: false
|
||||
t.string "secret"
|
||||
t.datetime "expires_at"
|
||||
t.boolean "confirmed", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "last_successful_delivery_at"
|
||||
t.string "domain"
|
||||
t.bigint "account_id", null: false
|
||||
t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true
|
||||
end
|
||||
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.string "name", default: "", null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
|
87
lib/json_ld/identity.rb
Normal file
87
lib/json_ld/identity.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# frozen_string_literal: true
|
||||
# This file generated automatically from http://w3id.org/identity/v1
|
||||
require 'json/ld'
|
||||
class JSON::LD::Context
|
||||
add_preloaded("http://w3id.org/identity/v1") do
|
||||
new(term_definitions: {
|
||||
"Credential" => TermDefinition.new("Credential", id: "https://w3id.org/credentials#Credential", simple: true),
|
||||
"CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
|
||||
"CryptographicKeyCredential" => TermDefinition.new("CryptographicKeyCredential", id: "https://w3id.org/credentials#CryptographicKeyCredential", simple: true),
|
||||
"EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true),
|
||||
"GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true),
|
||||
"Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
|
||||
"Identity" => TermDefinition.new("Identity", id: "https://w3id.org/identity#Identity", simple: true),
|
||||
"LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true),
|
||||
"Organization" => TermDefinition.new("Organization", id: "http://schema.org/Organization", simple: true),
|
||||
"Person" => TermDefinition.new("Person", id: "http://schema.org/Person", simple: true),
|
||||
"PostalAddress" => TermDefinition.new("PostalAddress", id: "http://schema.org/PostalAddress", simple: true),
|
||||
"about" => TermDefinition.new("about", id: "http://schema.org/about", type_mapping: "@id"),
|
||||
"accessControl" => TermDefinition.new("accessControl", id: "https://w3id.org/permissions#accessControl", type_mapping: "@id"),
|
||||
"address" => TermDefinition.new("address", id: "http://schema.org/address", type_mapping: "@id"),
|
||||
"addressCountry" => TermDefinition.new("addressCountry", id: "http://schema.org/addressCountry", simple: true),
|
||||
"addressLocality" => TermDefinition.new("addressLocality", id: "http://schema.org/addressLocality", simple: true),
|
||||
"addressRegion" => TermDefinition.new("addressRegion", id: "http://schema.org/addressRegion", simple: true),
|
||||
"cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true),
|
||||
"cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true),
|
||||
"cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true),
|
||||
"claim" => TermDefinition.new("claim", id: "https://w3id.org/credentials#claim", type_mapping: "@id"),
|
||||
"comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true),
|
||||
"created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
|
||||
"creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"),
|
||||
"cred" => TermDefinition.new("cred", id: "https://w3id.org/credentials#", simple: true, prefix: true),
|
||||
"credential" => TermDefinition.new("credential", id: "https://w3id.org/credentials#credential", type_mapping: "@id"),
|
||||
"dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true),
|
||||
"description" => TermDefinition.new("description", id: "http://schema.org/description", simple: true),
|
||||
"digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true),
|
||||
"digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true),
|
||||
"domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true),
|
||||
"email" => TermDefinition.new("email", id: "http://schema.org/email", simple: true),
|
||||
"expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
|
||||
"familyName" => TermDefinition.new("familyName", id: "http://schema.org/familyName", simple: true),
|
||||
"givenName" => TermDefinition.new("givenName", id: "http://schema.org/givenName", simple: true),
|
||||
"id" => TermDefinition.new("id", id: "@id", simple: true),
|
||||
"identity" => TermDefinition.new("identity", id: "https://w3id.org/identity#", simple: true, prefix: true),
|
||||
"identityService" => TermDefinition.new("identityService", id: "https://w3id.org/identity#identityService", type_mapping: "@id"),
|
||||
"idp" => TermDefinition.new("idp", id: "https://w3id.org/identity#idp", type_mapping: "@id"),
|
||||
"image" => TermDefinition.new("image", id: "http://schema.org/image", type_mapping: "@id"),
|
||||
"initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true),
|
||||
"issued" => TermDefinition.new("issued", id: "https://w3id.org/credentials#issued", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
|
||||
"issuer" => TermDefinition.new("issuer", id: "https://w3id.org/credentials#issuer", type_mapping: "@id"),
|
||||
"label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true),
|
||||
"member" => TermDefinition.new("member", id: "http://schema.org/member", type_mapping: "@id"),
|
||||
"memberOf" => TermDefinition.new("memberOf", id: "http://schema.org/memberOf", type_mapping: "@id"),
|
||||
"name" => TermDefinition.new("name", id: "http://schema.org/name", simple: true),
|
||||
"nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true),
|
||||
"normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true),
|
||||
"owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"),
|
||||
"password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true),
|
||||
"paymentProcessor" => TermDefinition.new("paymentProcessor", id: "https://w3id.org/payswarm#processor", simple: true),
|
||||
"perm" => TermDefinition.new("perm", id: "https://w3id.org/permissions#", simple: true, prefix: true),
|
||||
"postalCode" => TermDefinition.new("postalCode", id: "http://schema.org/postalCode", simple: true),
|
||||
"preferences" => TermDefinition.new("preferences", id: "https://w3id.org/payswarm#preferences", type_mapping: "@vocab"),
|
||||
"privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"),
|
||||
"privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true),
|
||||
"ps" => TermDefinition.new("ps", id: "https://w3id.org/payswarm#", simple: true, prefix: true),
|
||||
"publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"),
|
||||
"publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true),
|
||||
"publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"),
|
||||
"rdf" => TermDefinition.new("rdf", id: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", simple: true, prefix: true),
|
||||
"rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true),
|
||||
"recipient" => TermDefinition.new("recipient", id: "https://w3id.org/credentials#recipient", type_mapping: "@id"),
|
||||
"revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
|
||||
"schema" => TermDefinition.new("schema", id: "http://schema.org/", simple: true, prefix: true),
|
||||
"sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true),
|
||||
"signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true),
|
||||
"signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signatureAlgorithm", simple: true),
|
||||
"signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true),
|
||||
"streetAddress" => TermDefinition.new("streetAddress", id: "http://schema.org/streetAddress", simple: true),
|
||||
"title" => TermDefinition.new("title", id: "http://purl.org/dc/terms/title", simple: true),
|
||||
"type" => TermDefinition.new("type", id: "@type", simple: true),
|
||||
"url" => TermDefinition.new("url", id: "http://schema.org/url", type_mapping: "@id"),
|
||||
"writePermission" => TermDefinition.new("writePermission", id: "https://w3id.org/permissions#writePermission", type_mapping: "@id"),
|
||||
"xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
|
||||
})
|
||||
end
|
||||
alias_preloaded("https://w3id.org/identity/v1", "http://w3id.org/identity/v1")
|
||||
end
|
|
@ -211,7 +211,6 @@ module Mastodon
|
|||
end
|
||||
|
||||
option :concurrency, type: :numeric, default: 5, aliases: [:c]
|
||||
option :verbose, type: :boolean, aliases: [:v]
|
||||
option :dry_run, type: :boolean
|
||||
desc 'cull', 'Remove remote accounts that no longer exist'
|
||||
long_desc <<-LONG_DESC
|
||||
|
|
|
@ -15,7 +15,12 @@ module Mastodon
|
|||
end
|
||||
|
||||
def parallelize_with_progress(scope)
|
||||
ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency]
|
||||
if options[:concurrency] < 1
|
||||
say('Cannot run with this concurrency setting, must be at least 1', :red)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency] + 1
|
||||
|
||||
progress = create_progress_bar(scope.count)
|
||||
pool = Concurrent::FixedThreadPool.new(options[:concurrency])
|
||||
|
@ -27,17 +32,26 @@ module Mastodon
|
|||
|
||||
items.each do |item|
|
||||
futures << Concurrent::Future.execute(executor: pool) do
|
||||
ActiveRecord::Base.connection_pool.with_connection do
|
||||
begin
|
||||
progress.log("Processing #{item.id}") if options[:verbose]
|
||||
begin
|
||||
if !progress.total.nil? && progress.progress + 1 > progress.total
|
||||
# The number of items has changed between start and now,
|
||||
# since there is no good way to predict the final count from
|
||||
# here, just change the progress bar to an indeterminate one
|
||||
|
||||
result = yield(item)
|
||||
aggregate.increment(result) if result.is_a?(Integer)
|
||||
rescue => e
|
||||
progress.log pastel.red("Error processing #{item.id}: #{e}")
|
||||
ensure
|
||||
progress.increment
|
||||
progress.total = nil
|
||||
end
|
||||
|
||||
progress.log("Processing #{item.id}") if options[:verbose]
|
||||
|
||||
result = ActiveRecord::Base.connection_pool.with_connection do
|
||||
yield(item)
|
||||
end
|
||||
|
||||
aggregate.increment(result) if result.is_a?(Integer)
|
||||
rescue => e
|
||||
progress.log pastel.red("Error processing #{item.id}: #{e}")
|
||||
ensure
|
||||
progress.increment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -46,7 +60,7 @@ module Mastodon
|
|||
futures.map(&:value)
|
||||
end
|
||||
|
||||
progress.finish
|
||||
progress.stop
|
||||
|
||||
[total.value, aggregate.value]
|
||||
end
|
||||
|
|
|
@ -27,7 +27,6 @@ module Mastodon
|
|||
dry_run = options[:dry_run] ? '(DRY RUN)' : ''
|
||||
|
||||
if options[:all] || username.nil?
|
||||
|
||||
processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
|
||||
PrecomputeFeedService.new.call(account) unless options[:dry_run]
|
||||
end
|
||||
|
|
|
@ -50,6 +50,7 @@ module Mastodon
|
|||
option :concurrency, type: :numeric, default: 5, aliases: [:c]
|
||||
option :verbose, type: :boolean, default: false, aliases: [:v]
|
||||
option :dry_run, type: :boolean, default: false
|
||||
option :force, type: :boolean, default: false
|
||||
desc 'refresh', 'Fetch remote media files'
|
||||
long_desc <<-DESC
|
||||
Re-downloads media attachments from other servers. You must specify the
|
||||
|
@ -62,6 +63,9 @@ module Mastodon
|
|||
using username@domain handle of the account.
|
||||
|
||||
Use the --domain option to download attachments from a specific domain.
|
||||
|
||||
By default, attachments that are believed to be already downloaded will
|
||||
not be re-downloaded. To force re-download of every URL, use --force.
|
||||
DESC
|
||||
def refresh
|
||||
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
|
||||
|
@ -85,7 +89,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
processed, aggregate = parallelize_with_progress(scope) do |media_attachment|
|
||||
next if media_attachment.remote_url.blank?
|
||||
next if media_attachment.remote_url.blank? || (!options[:force] && media_attachment.file_file_name.present?)
|
||||
|
||||
unless options[:dry_run]
|
||||
media_attachment.reset_file!
|
||||
|
@ -97,5 +101,17 @@ module Mastodon
|
|||
|
||||
say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
|
||||
end
|
||||
|
||||
desc 'usage', 'Calculate disk space consumed by Mastodon'
|
||||
def usage
|
||||
say("Attachments:\t#{number_to_human_size(MediaAttachment.sum(:file_file_size))} (#{number_to_human_size(MediaAttachment.where(account: Account.local).sum(:file_file_size))} local)")
|
||||
say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
|
||||
say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
|
||||
say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
|
||||
say("Headers:\t#{number_to_human_size(Account.sum(:header_file_size))} (#{number_to_human_size(Account.local.sum(:header_file_size))} local)")
|
||||
say("Backups:\t#{number_to_human_size(Backup.sum(:dump_file_size))}")
|
||||
say("Imports:\t#{number_to_human_size(Import.sum(:data_file_size))}")
|
||||
say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def patch
|
||||
0
|
||||
1
|
||||
end
|
||||
|
||||
def flags
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
"stringz": "^2.0.0",
|
||||
"substring-trie": "^1.0.2",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"tesseract.js": "^2.0.0-alpha.15",
|
||||
"tesseract.js": "^2.0.0-alpha.16",
|
||||
"throng": "^4.0.0",
|
||||
"tiny-queue": "^0.2.1",
|
||||
"uuid": "^3.1.0",
|
||||
|
@ -177,7 +177,7 @@
|
|||
"babel-jest": "^24.9.0",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"eslint": "^6.4.0",
|
||||
"eslint": "^6.5.0",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "~6.2.3",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
|
|
|
@ -181,10 +181,6 @@ RSpec.describe SpamCheck do
|
|||
described_class.new(status2).flag!
|
||||
end
|
||||
|
||||
it 'silences the account' do
|
||||
expect(sender.silenced?).to be true
|
||||
end
|
||||
|
||||
it 'creates a report about the account' do
|
||||
expect(sender.targeted_reports.unresolved.count).to eq 1
|
||||
end
|
||||
|
|
|
@ -126,8 +126,8 @@ RSpec.describe Account, type: :model do
|
|||
end
|
||||
|
||||
it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
|
||||
expect(account.avatar_remote_url).to eq ''
|
||||
expect(account.header_remote_url).to eq ''
|
||||
expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar'
|
||||
expect(account.header_remote_url).to eq expectation.header_remote_url
|
||||
expect(account.avatar_file_name).to eq nil
|
||||
expect(account.header_file_name).to eq nil
|
||||
end
|
||||
|
|
|
@ -18,6 +18,8 @@ RSpec.describe Remotable do
|
|||
|
||||
def hoge=(arg); end
|
||||
|
||||
def hoge_file_name; end
|
||||
|
||||
def hoge_file_name=(arg); end
|
||||
|
||||
def has_attribute?(arg); end
|
||||
|
@ -109,12 +111,21 @@ RSpec.describe Remotable do
|
|||
end
|
||||
|
||||
context 'foo[attribute_name] == url' do
|
||||
it 'makes no request' do
|
||||
it 'makes no request if file is saved' do
|
||||
allow(foo).to receive(:[]).with(attribute_name).and_return(url)
|
||||
allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')
|
||||
|
||||
foo.hoge_remote_url = url
|
||||
expect(request).not_to have_been_requested
|
||||
end
|
||||
|
||||
it 'makes request if file is not saved' do
|
||||
allow(foo).to receive(:[]).with(attribute_name).and_return(url)
|
||||
allow(foo).to receive(:hoge_file_name).and_return(nil)
|
||||
|
||||
foo.hoge_remote_url = url
|
||||
expect(request).to have_been_requested
|
||||
end
|
||||
end
|
||||
|
||||
context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
|
||||
|
|
|
@ -34,11 +34,10 @@ RSpec.describe HomeFeed, type: :model do
|
|||
Redis.current.set("account:#{account.id}:regeneration", true)
|
||||
end
|
||||
|
||||
it 'gets statuses with ids in the range from database' do
|
||||
it 'returns nothing' do
|
||||
results = subject.get(3)
|
||||
|
||||
expect(results.map(&:id)).to eq [10, 3, 2]
|
||||
expect(results.first.attributes.keys).to include('id', 'updated_at')
|
||||
expect(results.map(&:id)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -333,49 +333,6 @@ RSpec.describe Status, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.as_home_timeline' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:followed) { Fabricate(:account) }
|
||||
let(:not_followed) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate(:follow, account: account, target_account: followed)
|
||||
|
||||
@self_status = Fabricate(:status, account: account, visibility: :public)
|
||||
@self_direct_status = Fabricate(:status, account: account, visibility: :direct)
|
||||
@followed_status = Fabricate(:status, account: followed, visibility: :public)
|
||||
@followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
|
||||
@not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
|
||||
|
||||
@results = Status.as_home_timeline(account)
|
||||
end
|
||||
|
||||
it 'includes statuses from self' do
|
||||
expect(@results).to include(@self_status)
|
||||
end
|
||||
|
||||
it 'does not include direct statuses from self' do
|
||||
expect(@results).to_not include(@self_direct_status)
|
||||
end
|
||||
|
||||
it 'includes statuses from followed' do
|
||||
expect(@results).to include(@followed_status)
|
||||
end
|
||||
|
||||
it 'does not include direct statuses mentioning recipient from followed' do
|
||||
Fabricate(:mention, account: account, status: @followed_direct_status)
|
||||
expect(@results).to_not include(@followed_direct_status)
|
||||
end
|
||||
|
||||
it 'does not include direct statuses not mentioning recipient from followed' do
|
||||
expect(@results).not_to include(@followed_direct_status)
|
||||
end
|
||||
|
||||
it 'does not include statuses from non-followed' do
|
||||
expect(@results).not_to include(@not_followed_status)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.as_direct_timeline' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:followed) { Fabricate(:account) }
|
||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -3921,10 +3921,10 @@ eslint@^2.7.0:
|
|||
text-table "~0.2.0"
|
||||
user-home "^2.0.0"
|
||||
|
||||
eslint@^6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.4.0.tgz#5aa9227c3fbe921982b2eda94ba0d7fae858611a"
|
||||
integrity sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==
|
||||
eslint@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.0.tgz#304623eec903969dd5c9f2d61c6ce3d6ecec8750"
|
||||
integrity sha512-IIbSW+vKOqMatPmS9ayyku4tvWxHY2iricSRtOz6+ZA5IPRlgXzEL0u/j6dr4eha0ugmhMwDTqxtmNu3kj9O4w==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
|
@ -4005,12 +4005,7 @@ esrecurse@^4.1.0:
|
|||
dependencies:
|
||||
estraverse "^4.1.0"
|
||||
|
||||
estraverse@^4.0.0, estraverse@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
|
||||
|
||||
estraverse@^4.1.0, estraverse@^4.1.1:
|
||||
estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
@ -7009,11 +7004,6 @@ node-fetch@^1.0.1:
|
|||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-fetch@^2.3.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
|
||||
node-forge@0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.2.tgz#b4bcc59fb12ce77a8825fc6a783dfe3182499c5a"
|
||||
|
@ -9398,21 +9388,16 @@ selfsigned@^1.10.6:
|
|||
dependencies:
|
||||
node-forge "0.8.2"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.1, semver@^5.7.0:
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
|
||||
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
semver@4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
|
||||
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
|
||||
|
||||
semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
|
@ -10103,10 +10088,10 @@ terser@^4.1.2:
|
|||
source-map "~0.6.1"
|
||||
source-map-support "~0.5.12"
|
||||
|
||||
tesseract.js-core@^2.0.0-beta.11:
|
||||
version "2.0.0-beta.11"
|
||||
resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.0.0-beta.11.tgz#c35e3e689efad30138603977ad7eaaac44c7fd37"
|
||||
integrity sha512-07haKH2JYYo0OfIJoioMS9dDiI5Hrl7+r1MqjeNAAT5WpKO0ATe4cpncC8s1kz0e3s1kaC5WOwL3YJcjbJE+hg==
|
||||
tesseract.js-core@^2.0.0-beta.12:
|
||||
version "2.0.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.0.0-beta.13.tgz#a21d798e88098898a9bdd935d0553215e03274f8"
|
||||
integrity sha512-GboWV/aV5h+Whito6L6Q3WCFZ2+lgxZGgjY84wSpWbTLEkkZgHsU+dz1or+3rWSABH/nuzHDco1bZRk5+f94mw==
|
||||
|
||||
tesseract.js-utils@^1.0.0-beta.8:
|
||||
version "1.0.0-beta.8"
|
||||
|
@ -10120,18 +10105,17 @@ tesseract.js-utils@^1.0.0-beta.8:
|
|||
is-url "^1.2.4"
|
||||
zlibjs "^0.3.1"
|
||||
|
||||
tesseract.js@^2.0.0-alpha.15:
|
||||
version "2.0.0-alpha.15"
|
||||
resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.0.0-alpha.15.tgz#9887f4d1c10e25bb098fde7a10580c865c362fad"
|
||||
integrity sha512-qM1XUFVlTO+tx6oVRpd9QQ8PwQLxo3qhbfIHByUlUVIqWx6y/U9xlHIaG033/Tjfs2EQ0NAehPTOJ+eNElsXEg==
|
||||
tesseract.js@^2.0.0-alpha.16:
|
||||
version "2.0.0-alpha.16"
|
||||
resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.0.0-alpha.16.tgz#1e17717234a1464481abe12283f2c3ac79603d2e"
|
||||
integrity sha512-8g3je2Kl8rkAFtpmwilGGj+8rCiPClNQaCjW6IafOPNn7hzFnVdL6fU6rG1Xsrc4Twv0HOa75kbpx5u70/WbTA==
|
||||
dependencies:
|
||||
axios "^0.18.0"
|
||||
check-types "^7.4.0"
|
||||
is-url "1.2.2"
|
||||
node-fetch "^2.3.0"
|
||||
opencollective-postinstall "^2.0.2"
|
||||
resolve-url "^0.2.1"
|
||||
tesseract.js-core "^2.0.0-beta.11"
|
||||
tesseract.js-core "^2.0.0-beta.12"
|
||||
tesseract.js-utils "^1.0.0-beta.8"
|
||||
|
||||
test-exclude@^5.0.0:
|
||||
|
|
Loading…
Add table
Reference in a new issue