commit
7d2e6429c2
@ -1,3 +1,9 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
[Issue text goes here].
|
||||
|
||||
* * * *
|
@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Push::SubscriptionsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :push }
|
||||
before_action :require_user!
|
||||
before_action :set_web_push_subscription
|
||||
|
||||
def create
|
||||
@web_subscription&.destroy!
|
||||
|
||||
@web_subscription = ::Web::PushSubscription.create!(
|
||||
endpoint: subscription_params[:endpoint],
|
||||
key_p256dh: subscription_params[:keys][:p256dh],
|
||||
key_auth: subscription_params[:keys][:auth],
|
||||
data: data_params,
|
||||
user_id: current_user.id,
|
||||
access_token_id: doorkeeper_token.id
|
||||
)
|
||||
|
||||
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
|
||||
|
||||
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
|
||||
|
||||
@web_subscription.update!(data: data_params)
|
||||
|
||||
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
@web_subscription&.destroy!
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_web_push_subscription
|
||||
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
|
||||
end
|
||||
|
||||
def subscription_params
|
||||
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
|
||||
end
|
||||
|
||||
def data_params
|
||||
return {} if params[:data].blank?
|
||||
params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::TrendsController < Api::BaseController
|
||||
before_action :set_tags
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = TrendingTags.get(limit_param(10))
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V2::SearchController < Api::V1::SearchController
|
||||
def index
|
||||
@search = Search.new(search)
|
||||
render json: @search, serializer: REST::V2::SearchSerializer
|
||||
end
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SessionTrackingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
UPDATE_SIGN_IN_HOURS = 24
|
||||
|
||||
included do
|
||||
before_action :set_session_activity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_session_activity
|
||||
return unless session_needs_update?
|
||||
current_session.touch
|
||||
end
|
||||
|
||||
def session_needs_update?
|
||||
!current_session.nil? && current_session.updated_at < UPDATE_SIGN_IN_HOURS.hours.ago
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Oauth::TokensController < Doorkeeper::TokensController
|
||||
def revoke
|
||||
unsubscribe_for_token if authorized? && token.accessible?
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unsubscribe_for_token
|
||||
Web::PushSubscription.where(access_token_id: token.id).delete_all
|
||||
end
|
||||
end
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
export default class LoadGap extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
maxId: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClick(this.props.maxId);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { disabled, intl } = this.props;
|
||||
|
||||
return (
|
||||
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
|
||||
<i className='fa fa-ellipsis-h' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from 'flavours/glitch/features/status/components/card';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
export default class CardContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
card: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { card, ...props } = this.props;
|
||||
return <Card card={fromJS(card)} {...props} />;
|
||||
}
|
||||
|
||||
}
|
@ -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 'mastodon/locales';
|
||||
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
||||
import Video from 'flavours/glitch/features/video';
|
||||
import Card from 'flavours/glitch/features/status/components/card';
|
||||
import ModalRoot from 'flavours/glitch/components/modal_root';
|
||||
import MediaModal from 'flavours/glitch/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 'mastodon/locales';
|
||||
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
||||
import ModalRoot from 'flavours/glitch/components/modal_root';
|
||||
import MediaModal from 'flavours/glitch/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 'mastodon/locales';
|
||||
import Video from 'flavours/glitch/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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import Motion from 'flavours/glitch/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
// This is the spring used with our motion.
|
||||
const motionSpring = spring(1, { damping: 35, stiffness: 400 });
|
||||
|
||||
// Messages.
|
||||
const messages = defineMessages({
|
||||
disclaimer: {
|
||||
defaultMessage: 'This toot will only be sent to all the mentioned users.',
|
||||
id: 'compose_form.direct_message_warning',
|
||||
},
|
||||
learn_more: {
|
||||
defaultMessage: 'Learn more',
|
||||
id: 'compose_form.direct_message_warning_learn_more'
|
||||
}
|
||||
});
|
||||
|
||||
// The component.
|
||||
export default function ComposerDirectWarning () {
|
||||
return (
|
||||
<Motion
|
||||
defaultStyle={{
|
||||
opacity: 0,
|
||||
scaleX: 0.85,
|
||||
scaleY: 0.75,
|
||||
}}
|
||||
style={{
|
||||
opacity: motionSpring,
|
||||
scaleX: motionSpring,
|
||||
scaleY: motionSpring,
|
||||
}}
|
||||
>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
<div
|
||||
className='composer--warning'
|
||||
style={{
|
||||
opacity: opacity,
|
||||
transform: `scale(${scaleX}, ${scaleY})`,
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<FormattedMessage {...messages.disclaimer} /> <a href='/terms' target='_blank'><FormattedMessage {...messages.learn_more} /></a>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
ComposerDirectWarning.propTypes = {};
|
@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
||||
import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connectCommunityStream } from 'flavours/glitch/actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
|
||||
});
|
||||
|
||||
@connect()
|
||||
@injectIntl
|
||||
export default class CommunityTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(expandCommunityTimeline());
|
||||
this.disconnect = dispatch(connectCommunityStream());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandCommunityTimeline({ maxId }));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<ColumnHeader
|
||||
icon='users'
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={this.handleHeaderClick}
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
timelineId='community'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
scrollKey='standalone_public_timeline'
|
||||
trackScroll={false}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue