Merge branch 'master' into glitch-soc/merge
commit
ba7ee67498
3
Gemfile
3
Gemfile
|
@ -42,7 +42,7 @@ gem 'omniauth-cas', '~> 1.1'
|
||||||
gem 'omniauth-saml', '~> 1.10'
|
gem 'omniauth-saml', '~> 1.10'
|
||||||
gem 'omniauth', '~> 1.2'
|
gem 'omniauth', '~> 1.2'
|
||||||
|
|
||||||
gem 'doorkeeper', '~> 4.3'
|
gem 'doorkeeper', '~> 4.2', '< 4.3'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'goldfinger', '~> 2.1'
|
gem 'goldfinger', '~> 2.1'
|
||||||
|
@ -52,6 +52,7 @@ gem 'html2text'
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 3.2'
|
gem 'http', '~> 3.2'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
|
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
|
||||||
gem 'httplog', '~> 1.0'
|
gem 'httplog', '~> 1.0'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.1'
|
gem 'kaminari', '~> 1.1'
|
||||||
|
|
13
Gemfile.lock
13
Gemfile.lock
|
@ -1,3 +1,10 @@
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/tmm1/http_parser.rb
|
||||||
|
revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||||
|
ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||||
|
specs:
|
||||||
|
http_parser.rb (0.6.1)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
@ -167,7 +174,7 @@ GEM
|
||||||
docile (1.3.0)
|
docile (1.3.0)
|
||||||
domain_name (0.5.20180417)
|
domain_name (0.5.20180417)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (4.3.2)
|
doorkeeper (4.2.6)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
dotenv (2.2.2)
|
dotenv (2.2.2)
|
||||||
dotenv-rails (2.2.2)
|
dotenv-rails (2.2.2)
|
||||||
|
@ -254,7 +261,6 @@ GEM
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (2.1.0)
|
http-form_data (2.1.0)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
http_parser.rb (0.6.0)
|
|
||||||
httplog (1.0.2)
|
httplog (1.0.2)
|
||||||
colorize (~> 0.8)
|
colorize (~> 0.8)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
|
@ -661,7 +667,7 @@ DEPENDENCIES
|
||||||
devise (~> 4.4)
|
devise (~> 4.4)
|
||||||
devise-two-factor (~> 3.0)
|
devise-two-factor (~> 3.0)
|
||||||
devise_pam_authenticatable2 (~> 9.1)
|
devise_pam_authenticatable2 (~> 9.1)
|
||||||
doorkeeper (~> 4.3)
|
doorkeeper (~> 4.2, < 4.3)
|
||||||
dotenv-rails (~> 2.2, < 2.3)
|
dotenv-rails (~> 2.2, < 2.3)
|
||||||
fabrication (~> 2.20)
|
fabrication (~> 2.20)
|
||||||
faker (~> 1.8)
|
faker (~> 1.8)
|
||||||
|
@ -678,6 +684,7 @@ DEPENDENCIES
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
http (~> 3.2)
|
http (~> 3.2)
|
||||||
http_accept_language (~> 2.1)
|
http_accept_language (~> 2.1)
|
||||||
|
http_parser.rb (~> 0.6)!
|
||||||
httplog (~> 1.0)
|
httplog (~> 1.0)
|
||||||
i18n-tasks (~> 0.9)
|
i18n-tasks (~> 0.9)
|
||||||
idn-ruby
|
idn-ruby
|
||||||
|
|
|
@ -20,6 +20,12 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
|
||||||
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
|
||||||
|
|
||||||
|
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
|
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,16 @@ module Admin::AccountModerationNotesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def admin_account_inline_link_to(account)
|
||||||
|
link_to admin_account_path(account.id), class: name_tag_classes(account, true) do
|
||||||
|
content_tag(:span, account.acct, class: 'username')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def name_tag_classes(account)
|
def name_tag_classes(account, inline = false)
|
||||||
classes = ['name-tag']
|
classes = [inline ? 'inline-name-tag' : 'name-tag']
|
||||||
classes << 'suspended' if account.suspended?
|
classes << 'suspended' if account.suspended?
|
||||||
classes.join(' ')
|
classes.join(' ')
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,18 +52,22 @@ module JsonLdHelper
|
||||||
graph.dump(:normalize)
|
graph.dump(:normalize)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_resource(uri, id)
|
def fetch_resource(uri, id, on_behalf_of = nil)
|
||||||
unless id
|
unless id
|
||||||
json = fetch_resource_without_id_validation(uri)
|
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||||
return unless json
|
return unless json
|
||||||
uri = json['id']
|
uri = json['id']
|
||||||
end
|
end
|
||||||
|
|
||||||
json = fetch_resource_without_id_validation(uri)
|
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||||
json.present? && json['id'] == uri ? json : nil
|
json.present? && json['id'] == uri ? json : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_resource_without_id_validation(uri)
|
def fetch_resource_without_id_validation(uri, on_behalf_of = nil)
|
||||||
|
build_request(uri, on_behalf_of).perform do |response|
|
||||||
|
return body_to_json(response.body_with_limit) if response.code == 200
|
||||||
|
end
|
||||||
|
# If request failed, retry without doing it on behalf of a user
|
||||||
build_request(uri).perform do |response|
|
build_request(uri).perform do |response|
|
||||||
response.code == 200 ? body_to_json(response.body_with_limit) : nil
|
response.code == 200 ? body_to_json(response.body_with_limit) : nil
|
||||||
end
|
end
|
||||||
|
@ -85,8 +89,9 @@ module JsonLdHelper
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_request(uri)
|
def build_request(uri, on_behalf_of = nil)
|
||||||
request = Request.new(:get, uri)
|
request = Request.new(:get, uri)
|
||||||
|
request.on_behalf_of(on_behalf_of) if on_behalf_of
|
||||||
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
||||||
request
|
request
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,7 @@ module SettingsHelper
|
||||||
'pt-BR': 'Português do Brasil',
|
'pt-BR': 'Português do Brasil',
|
||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
sk: 'Slovensky',
|
sk: 'Slovensky',
|
||||||
|
sl: 'Slovenščina',
|
||||||
sr: 'Српски',
|
sr: 'Српски',
|
||||||
'sr-Latn': 'Srpski (latinica)',
|
'sr-Latn': 'Srpski (latinica)',
|
||||||
sv: 'Svenska',
|
sv: 'Svenska',
|
||||||
|
|
|
@ -84,8 +84,8 @@ export default class Status extends ImmutablePureComponent {
|
||||||
return <div className='media-spoiler-video' style={{ height: '110px' }} />;
|
return <div className='media-spoiler-video' style={{ height: '110px' }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = startTime => {
|
handleOpenVideo = (media, startTime) => {
|
||||||
this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime);
|
this.props.onOpenVideo(media, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyReply = e => {
|
handleHotkeyReply = e => {
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import React, { Fragment } from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
|
||||||
import { getLocale } from '../locales';
|
|
||||||
import Card from '../features/status/components/card';
|
|
||||||
import ModalRoot from '../components/modal_root';
|
|
||||||
import MediaModal from '../features/ui/components/media_modal';
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
|
||||||
addLocaleData(localeData);
|
|
||||||
|
|
||||||
export default class CardsContainer extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
locale: PropTypes.string,
|
|
||||||
cards: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
media: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOpenCard = (media) => {
|
|
||||||
document.body.classList.add('card-standalone__body');
|
|
||||||
this.setState({ media });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCloseCard = () => {
|
|
||||||
document.body.classList.remove('card-standalone__body');
|
|
||||||
this.setState({ media: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { locale, cards } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IntlProvider locale={locale} messages={messages}>
|
|
||||||
<Fragment>
|
|
||||||
{[].map.call(cards, container => {
|
|
||||||
const { card, ...props } = JSON.parse(container.getAttribute('data-props'));
|
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
|
||||||
<Card card={fromJS(card)} onOpenMedia={this.handleOpenCard} {...props} />,
|
|
||||||
container,
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<ModalRoot onClose={this.handleCloseCard}>
|
|
||||||
{this.state.media && (
|
|
||||||
<MediaModal media={this.state.media} index={0} onClose={this.handleCloseCard} />
|
|
||||||
)}
|
|
||||||
</ModalRoot>
|
|
||||||
</Fragment>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
|
import { getLocale } from '../locales';
|
||||||
|
import MediaGallery from '../components/media_gallery';
|
||||||
|
import Video from '../features/video';
|
||||||
|
import Card from '../features/status/components/card';
|
||||||
|
import ModalRoot from '../components/modal_root';
|
||||||
|
import MediaModal from '../features/ui/components/media_modal';
|
||||||
|
import { List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
const { localeData, messages } = getLocale();
|
||||||
|
addLocaleData(localeData);
|
||||||
|
|
||||||
|
const MEDIA_COMPONENTS = { MediaGallery, Video, Card };
|
||||||
|
|
||||||
|
export default class MediaContainer extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
locale: PropTypes.string.isRequired,
|
||||||
|
components: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
media: null,
|
||||||
|
index: null,
|
||||||
|
time: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOpenMedia = (media, index) => {
|
||||||
|
document.body.classList.add('media-standalone__body');
|
||||||
|
this.setState({ media, index });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpenVideo = (video, time) => {
|
||||||
|
const media = ImmutableList([video]);
|
||||||
|
|
||||||
|
document.body.classList.add('media-standalone__body');
|
||||||
|
this.setState({ media, time });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseMedia = () => {
|
||||||
|
document.body.classList.remove('media-standalone__body');
|
||||||
|
this.setState({ media: null, index: null, time: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { locale, components } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale} messages={messages}>
|
||||||
|
<Fragment>
|
||||||
|
{[].map.call(components, (component, i) => {
|
||||||
|
const componentName = component.getAttribute('data-component');
|
||||||
|
const Component = MEDIA_COMPONENTS[componentName];
|
||||||
|
const { media, card, ...props } = JSON.parse(component.getAttribute('data-props'));
|
||||||
|
|
||||||
|
Object.assign(props, {
|
||||||
|
...(media ? { media: fromJS(media) } : {}),
|
||||||
|
...(card ? { card: fromJS(card) } : {}),
|
||||||
|
|
||||||
|
...(componentName === 'Video' ? {
|
||||||
|
onOpenVideo: this.handleOpenVideo,
|
||||||
|
} : {
|
||||||
|
onOpenMedia: this.handleOpenMedia,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(
|
||||||
|
<Component {...props} key={`media-${i}`} />,
|
||||||
|
component,
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<ModalRoot onClose={this.handleCloseMedia}>
|
||||||
|
{this.state.media && (
|
||||||
|
<MediaModal
|
||||||
|
media={this.state.media}
|
||||||
|
index={this.state.index || 0}
|
||||||
|
time={this.state.time}
|
||||||
|
onClose={this.handleCloseMedia}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ModalRoot>
|
||||||
|
</Fragment>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,68 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
|
||||||
import { getLocale } from '../locales';
|
|
||||||
import MediaGallery from '../components/media_gallery';
|
|
||||||
import ModalRoot from '../components/modal_root';
|
|
||||||
import MediaModal from '../features/ui/components/media_modal';
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
|
||||||
addLocaleData(localeData);
|
|
||||||
|
|
||||||
export default class MediaGalleriesContainer extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
locale: PropTypes.string.isRequired,
|
|
||||||
galleries: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
media: null,
|
|
||||||
index: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
|
||||||
document.body.classList.add('media-gallery-standalone__body');
|
|
||||||
this.setState({ media, index });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCloseMedia = () => {
|
|
||||||
document.body.classList.remove('media-gallery-standalone__body');
|
|
||||||
this.setState({ media: null, index: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { locale, galleries } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IntlProvider locale={locale} messages={messages}>
|
|
||||||
<React.Fragment>
|
|
||||||
{[].map.call(galleries, gallery => {
|
|
||||||
const { media, ...props } = JSON.parse(gallery.getAttribute('data-props'));
|
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
|
||||||
<MediaGallery
|
|
||||||
{...props}
|
|
||||||
media={fromJS(media)}
|
|
||||||
onOpenMedia={this.handleOpenMedia}
|
|
||||||
/>,
|
|
||||||
gallery
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<ModalRoot onClose={this.handleCloseMedia}>
|
|
||||||
{this.state.media === null || this.state.index === null ? null : (
|
|
||||||
<MediaModal
|
|
||||||
media={this.state.media}
|
|
||||||
index={this.state.index}
|
|
||||||
onClose={this.handleCloseMedia}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ModalRoot>
|
|
||||||
</React.Fragment>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
|
||||||
import { getLocale } from '../locales';
|
|
||||||
import Video from '../features/video';
|
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
|
||||||
addLocaleData(localeData);
|
|
||||||
|
|
||||||
export default class VideoContainer extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
locale: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { locale, ...props } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IntlProvider locale={locale} messages={messages}>
|
|
||||||
<Video {...props} />
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -34,8 +34,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = startTime => {
|
handleOpenVideo = (media, startTime) => {
|
||||||
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
|
this.props.onOpenVideo(media, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExpandedToggle = () => {
|
handleExpandedToggle = () => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import ReactSwipeableViews from 'react-swipeable-views';
|
import ReactSwipeableViews from 'react-swipeable-views';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import Video from '../../video';
|
||||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
@ -112,6 +113,22 @@ export default class MediaModal extends ImmutablePureComponent {
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (image.get('type') === 'video') {
|
||||||
|
const { time } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Video
|
||||||
|
preview={image.get('preview_url')}
|
||||||
|
src={image.get('url')}
|
||||||
|
width={image.get('width')}
|
||||||
|
height={image.get('height')}
|
||||||
|
startTime={time || 0}
|
||||||
|
onCloseVideo={onClose}
|
||||||
|
detailed
|
||||||
|
description={image.get('description')}
|
||||||
|
key={image.get('url')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (image.get('type') === 'gifv') {
|
} else if (image.get('type') === 'gifv') {
|
||||||
return (
|
return (
|
||||||
<ExtendedVideoPlayer
|
<ExtendedVideoPlayer
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
@ -131,6 +132,8 @@ export default class Video extends React.PureComponent {
|
||||||
this.seek = c;
|
this.seek = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickRoot = e => e.stopPropagation();
|
||||||
|
|
||||||
handlePlay = () => {
|
handlePlay = () => {
|
||||||
this.setState({ paused: false });
|
this.setState({ paused: false });
|
||||||
}
|
}
|
||||||
|
@ -244,8 +247,17 @@ export default class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
|
const { src, preview, width, height } = this.props;
|
||||||
|
const media = fromJS({
|
||||||
|
type: 'video',
|
||||||
|
url: src,
|
||||||
|
preview_url: preview,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
|
||||||
this.video.pause();
|
this.video.pause();
|
||||||
this.props.onOpenVideo(this.video.currentTime);
|
this.props.onOpenVideo(media, this.video.currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseVideo = () => {
|
handleCloseVideo = () => {
|
||||||
|
@ -270,7 +282,16 @@ export default class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })} style={playerStyle} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
<div
|
||||||
|
role='menuitem'
|
||||||
|
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })}
|
||||||
|
style={playerStyle}
|
||||||
|
ref={this.setPlayerRef}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
onClick={this.handleClickRoot}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref={this.setVideoRef}
|
ref={this.setVideoRef}
|
||||||
src={src}
|
src={src}
|
||||||
|
|
|
@ -28,11 +28,10 @@ self.addEventListener('fetch', function(event) {
|
||||||
const asyncResponse = fetchRoot();
|
const asyncResponse = fetchRoot();
|
||||||
const asyncCache = openWebCache();
|
const asyncCache = openWebCache();
|
||||||
|
|
||||||
event.respondWith(asyncResponse.then(async response => {
|
event.respondWith(asyncResponse.then(response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const cache = await asyncCache;
|
return asyncCache.then(cache => cache.put('/', response))
|
||||||
await cache.put('/', response);
|
.then(() => response.clone());
|
||||||
return response.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw null;
|
throw null;
|
||||||
|
@ -41,35 +40,38 @@ self.addEventListener('fetch', function(event) {
|
||||||
const asyncResponse = fetch(event.request);
|
const asyncResponse = fetch(event.request);
|
||||||
const asyncCache = openWebCache();
|
const asyncCache = openWebCache();
|
||||||
|
|
||||||
event.respondWith(asyncResponse.then(async response => {
|
event.respondWith(asyncResponse.then(response => {
|
||||||
if (response.ok || response.type === 'opaqueredirect') {
|
if (response.ok || response.type === 'opaqueredirect') {
|
||||||
await Promise.all([
|
return Promise.all([
|
||||||
asyncCache.then(cache => cache.delete('/')),
|
asyncCache.then(cache => cache.delete('/')),
|
||||||
indexedDB.deleteDatabase('mastodon'),
|
indexedDB.deleteDatabase('mastodon'),
|
||||||
]);
|
]).then(() => response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}));
|
}));
|
||||||
} else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
|
} else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
|
||||||
event.respondWith(openSystemCache().then(async cache => {
|
event.respondWith(openSystemCache().then(cache => {
|
||||||
const cached = await cache.match(event.request.url);
|
return cache.match(event.request.url).then(cached => {
|
||||||
|
if (cached === undefined) {
|
||||||
|
return fetch(event.request).then(fetched => {
|
||||||
|
if (fetched.ok) {
|
||||||
|
const put = cache.put(event.request.url, fetched.clone());
|
||||||
|
|
||||||
if (cached === undefined) {
|
put.catch(() => freeStorage());
|
||||||
const fetched = await fetch(event.request);
|
|
||||||
|
|
||||||
if (fetched.ok) {
|
return put.then(() => {
|
||||||
try {
|
freeStorage();
|
||||||
await cache.put(event.request.url, fetched.clone());
|
return fetched;
|
||||||
} finally {
|
});
|
||||||
freeStorage();
|
}
|
||||||
}
|
|
||||||
|
return fetched;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetched;
|
return cached;
|
||||||
}
|
});
|
||||||
|
|
||||||
return cached;
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -182,7 +182,9 @@ export function putStatuses(records) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function freeStorage() {
|
export function freeStorage() {
|
||||||
return navigator.storage.estimate().then(({ quota, usage }) => {
|
// navigator.storage is not present on:
|
||||||
|
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.100 Safari/537.36 Edge/16.16299
|
||||||
|
return 'storage' in navigator && navigator.storage.estimate().then(({ quota, usage }) => {
|
||||||
if (usage + storageMargin < quota) {
|
if (usage + storageMargin < quota) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ function main() {
|
||||||
const emojify = require('../mastodon/features/emoji/emoji').default;
|
const emojify = require('../mastodon/features/emoji/emoji').default;
|
||||||
const { getLocale } = require('../mastodon/locales');
|
const { getLocale } = require('../mastodon/locales');
|
||||||
const { localeData } = getLocale();
|
const { localeData } = getLocale();
|
||||||
const VideoContainer = require('../mastodon/containers/video_container').default;
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
|
|
||||||
|
@ -51,30 +50,16 @@ function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
[].forEach.call(document.querySelectorAll('[data-component="Video"]'), (content) => {
|
const reactComponents = document.querySelectorAll('[data-component]');
|
||||||
const props = JSON.parse(content.getAttribute('data-props'));
|
if (reactComponents.length > 0) {
|
||||||
ReactDOM.render(<VideoContainer locale={locale} {...props} />, content);
|
import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container')
|
||||||
});
|
.then(({ default: MediaContainer }) => {
|
||||||
|
const content = document.createElement('div');
|
||||||
|
|
||||||
const cards = document.querySelectorAll('[data-component="Card"]');
|
ReactDOM.render(<MediaContainer locale={locale} components={reactComponents} />, content);
|
||||||
|
document.body.appendChild(content);
|
||||||
if (cards.length > 0) {
|
})
|
||||||
import(/* webpackChunkName: "containers/cards_container" */ '../mastodon/containers/cards_container').then(({ default: CardsContainer }) => {
|
.catch(error => console.error(error));
|
||||||
const content = document.createElement('div');
|
|
||||||
|
|
||||||
ReactDOM.render(<CardsContainer locale={locale} cards={cards} />, content);
|
|
||||||
document.body.appendChild(content);
|
|
||||||
}).catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
const mediaGalleries = document.querySelectorAll('[data-component="MediaGallery"]');
|
|
||||||
|
|
||||||
if (mediaGalleries.length > 0) {
|
|
||||||
const MediaGalleriesContainer = require('../mastodon/containers/media_galleries_container').default;
|
|
||||||
const content = document.createElement('div');
|
|
||||||
|
|
||||||
ReactDOM.render(<MediaGalleriesContainer locale={locale} galleries={mediaGalleries} />, content);
|
|
||||||
document.body.appendChild(content);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -484,19 +484,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a.name-tag,
|
a.name-tag,
|
||||||
.name-tag {
|
.name-tag,
|
||||||
display: flex;
|
a.inline-name-tag,
|
||||||
align-items: center;
|
.inline-name-tag {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
|
|
||||||
.avatar {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
margin-right: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
@ -514,6 +507,26 @@ a.name-tag,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.name-tag,
|
||||||
|
.name-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.suspended {
|
||||||
|
.avatar {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.speech-bubble {
|
.speech-bubble {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-left: 4px solid $ui-highlight-color;
|
border-left: 4px solid $ui-highlight-color;
|
||||||
|
|
|
@ -4432,6 +4432,10 @@ a.status-card {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
|
|
@ -60,8 +60,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-standalone__body,
|
.media-standalone__body {
|
||||||
.media-gallery-standalone__body {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
@keyframes Swag {
|
||||||
|
0% { background-position: 0% 0%; }
|
||||||
|
50% { background-position: 100% 0%; }
|
||||||
|
100% { background-position: 200% 0%; }
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -187,6 +193,11 @@ a.table-action-link {
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
background: linear-gradient(to right, orange , yellow, green, cyan, blue, violet,orange , yellow, green, cyan, blue, violet);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
animation: Swag 2s linear 0s infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
if object_uri.start_with?('http')
|
if object_uri.start_with?('http')
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
|
|
||||||
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true)
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
||||||
elsif @object['url'].present?
|
elsif @object['url'].present?
|
||||||
::FetchRemoteStatusService.new.call(@object['url'])
|
::FetchRemoteStatusService.new.call(@object['url'])
|
||||||
end
|
end
|
||||||
|
|
|
@ -74,16 +74,7 @@ module StatusThreadingConcern
|
||||||
statuses = statuses_with_accounts(ids).to_a
|
statuses = statuses_with_accounts(ids).to_a
|
||||||
account_ids = statuses.map(&:account_id).uniq
|
account_ids = statuses.map(&:account_id).uniq
|
||||||
domains = statuses.map(&:account_domain).compact.uniq
|
domains = statuses.map(&:account_domain).compact.uniq
|
||||||
|
relations = relations_map_for_account(account, account_ids, domains)
|
||||||
relations = if account.present?
|
|
||||||
{
|
|
||||||
blocking: Account.blocking_map(account_ids, account.id),
|
|
||||||
blocked_by: Account.blocked_by_map(account_ids, account.id),
|
|
||||||
muting: Account.muting_map(account_ids, account.id),
|
|
||||||
following: Account.following_map(account_ids, account.id),
|
|
||||||
domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
statuses.reject! { |status| filter_from_context?(status, account, relations) }
|
statuses.reject! { |status| filter_from_context?(status, account, relations) }
|
||||||
|
|
||||||
|
@ -91,6 +82,18 @@ module StatusThreadingConcern
|
||||||
statuses.sort_by! { |status| ids.index(status.id) }
|
statuses.sort_by! { |status| ids.index(status.id) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def relations_map_for_account(account, account_ids, domains)
|
||||||
|
return {} if account.nil?
|
||||||
|
|
||||||
|
{
|
||||||
|
blocking: Account.blocking_map(account_ids, account.id),
|
||||||
|
blocked_by: Account.blocked_by_map(account_ids, account.id),
|
||||||
|
muting: Account.muting_map(account_ids, account.id),
|
||||||
|
following: Account.following_map(account_ids, account.id),
|
||||||
|
domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def statuses_with_accounts(ids)
|
def statuses_with_accounts(ids)
|
||||||
Status.where(id: ids).includes(:account)
|
Status.where(id: ids).includes(:account)
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,9 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
# Should be called when uri has already been checked for locality
|
# Should be called when uri has already been checked for locality
|
||||||
def call(uri, id: true, prefetched_body: nil)
|
def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
|
||||||
@json = if prefetched_body.nil?
|
@json = if prefetched_body.nil?
|
||||||
fetch_resource(uri, id)
|
fetch_resource(uri, id, on_behalf_of)
|
||||||
else
|
else
|
||||||
body_to_json(prefetched_body)
|
body_to_json(prefetched_body)
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ class PostStatusService < BaseService
|
||||||
media = validate_media!(options[:media_ids])
|
media = validate_media!(options[:media_ids])
|
||||||
status = nil
|
status = nil
|
||||||
text = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
|
text = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
|
||||||
text = '.' if text.blank? && !media.empty?
|
text = '.' if text.blank? && media.present?
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
status = account.statuses.create!(text: text,
|
status = account.statuses.create!(text: text,
|
||||||
|
|
|
@ -3,26 +3,30 @@
|
||||||
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
|
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
|
||||||
.batch-table__row__content
|
.batch-table__row__content
|
||||||
.status__content><
|
.status__content><
|
||||||
- unless status.spoiler_text.blank?
|
- unless status.proper.spoiler_text.blank?
|
||||||
%p><
|
%p><
|
||||||
%strong= Formatter.instance.format_spoiler(status)
|
%strong> Content warning: #{Formatter.instance.format_spoiler(status.proper)}
|
||||||
|
|
||||||
= Formatter.instance.format(status, custom_emojify: true)
|
= Formatter.instance.format(status.proper, custom_emojify: true)
|
||||||
|
|
||||||
- unless status.media_attachments.empty?
|
- unless status.proper.media_attachments.empty?
|
||||||
- if status.media_attachments.first.video?
|
- if status.proper.media_attachments.first.video?
|
||||||
- video = status.media_attachments.first
|
- video = status.proper.media_attachments.first
|
||||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
|
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
|
||||||
- else
|
- else
|
||||||
= react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
= react_component :media_gallery, height: 343, sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||||
|
|
||||||
.detailed-status__meta
|
.detailed-status__meta
|
||||||
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
|
= link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
|
||||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||||
·
|
·
|
||||||
= fa_visibility_icon(status)
|
- if status.reblog?
|
||||||
= t("statuses.visibilities.#{status.visibility}")
|
= fa_icon('retweet fw')
|
||||||
- if status.sensitive?
|
= t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account))
|
||||||
|
- else
|
||||||
|
= fa_visibility_icon(status)
|
||||||
|
= t("statuses.visibilities.#{status.visibility}")
|
||||||
|
- if status.proper.sensitive?
|
||||||
·
|
·
|
||||||
= fa_icon('eye-slash fw')
|
= fa_icon('eye-slash fw')
|
||||||
= t('stream_entries.sensitive_content')
|
= t('stream_entries.sensitive_content')
|
||||||
|
|
|
@ -68,6 +68,7 @@ module Mastodon
|
||||||
:'pt-BR',
|
:'pt-BR',
|
||||||
:ru,
|
:ru,
|
||||||
:sk,
|
:sk,
|
||||||
|
:sl,
|
||||||
:sr,
|
:sr,
|
||||||
:'sr-Latn',
|
:'sr-Latn',
|
||||||
:sv,
|
:sv,
|
||||||
|
|
|
@ -693,6 +693,7 @@ en:
|
||||||
video:
|
video:
|
||||||
one: "%{count} video"
|
one: "%{count} video"
|
||||||
other: "%{count} videos"
|
other: "%{count} videos"
|
||||||
|
boosted_from_html: Boosted from %{acct_link}
|
||||||
content_warning: 'Content warning: %{warning}'
|
content_warning: 'Content warning: %{warning}'
|
||||||
disallowed_hashtags:
|
disallowed_hashtags:
|
||||||
one: 'contained a disallowed hashtag: %{tags}'
|
one: 'contained a disallowed hashtag: %{tags}'
|
||||||
|
|
|
@ -326,7 +326,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :push do
|
namespace :push do
|
||||||
resource :subscription, only: [:create, :update, :destroy]
|
resource :subscription, only: [:create, :show, :update, :destroy]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,10 @@ module.exports = {
|
||||||
settings,
|
settings,
|
||||||
core,
|
core,
|
||||||
flavours,
|
flavours,
|
||||||
env,
|
env: {
|
||||||
|
CDN_HOST: env.CDN_HOST,
|
||||||
|
NODE_ENV: env.NODE_ENV,
|
||||||
|
},
|
||||||
loadersDir,
|
loadersDir,
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
const sharedConfig = require('./shared.js');
|
const sharedConfig = require('./shared.js');
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
const OfflinePlugin = require('offline-plugin');
|
const OfflinePlugin = require('offline-plugin');
|
||||||
const { publicPath } = require('./configuration.js');
|
const { env, publicPath } = require('./configuration.js');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
let compressionAlgorithm;
|
let compressionAlgorithm;
|
||||||
|
@ -90,7 +90,7 @@ module.exports = merge(sharedConfig, {
|
||||||
'**/*.woff',
|
'**/*.woff',
|
||||||
],
|
],
|
||||||
ServiceWorker: {
|
ServiceWorker: {
|
||||||
entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(process.env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`,
|
entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`,
|
||||||
cacheName: 'mastodon',
|
cacheName: 'mastodon',
|
||||||
output: '../assets/sw.js',
|
output: '../assets/sw.js',
|
||||||
publicPath: '/sw.js',
|
publicPath: '/sw.js',
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ImproveIndexOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migration[5.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_index :statuses, [:account_id, :id, :visibility], where: 'visibility IN (0, 1, 2)', algorithm: :concurrently
|
||||||
|
add_index :statuses, [:account_id, :id], where: 'visibility = 3', algorithm: :concurrently
|
||||||
|
remove_index :statuses, column: [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RevertIndexChangeOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migration[5.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
safety_assured do
|
||||||
|
add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_index :statuses, column: [:account_id, :id, :visibility], where: 'visibility IN (0, 1, 2)', algorithm: :concurrently
|
||||||
|
remove_index :statuses, column: [:account_id, :id], where: 'visibility = 3', algorithm: :concurrently
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2018_05_10_230049) do
|
ActiveRecord::Schema.define(version: 2018_05_14_140000) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def flags
|
def flags
|
||||||
'rc1'
|
'rc3'
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_a
|
def to_a
|
||||||
|
|
Loading…
Reference in New Issue