commit
36e5c9d45b
@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!
|
||||
before_action :set_items
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 0, public: false
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def uri_prefix
|
||||
signed_request_account.uri[/http(s?):\/\/[^\/]+\//]
|
||||
end
|
||||
|
||||
def set_items
|
||||
@items = @account.followers.where(Account.arel_table[:uri].matches(uri_prefix + '%', false, true)).pluck(:uri)
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_followers_synchronization_url(@account),
|
||||
type: :ordered,
|
||||
items: @items
|
||||
)
|
||||
end
|
||||
end
|
@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class IpBlocksController < BaseController
|
||||
def index
|
||||
authorize :ip_block, :index?
|
||||
|
||||
@ip_blocks = IpBlock.page(params[:page])
|
||||
@form = Form::IpBlockBatch.new
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :ip_block, :create?
|
||||
|
||||
@ip_block = IpBlock.new(ip: '', severity: :no_access, expires_in: 1.year)
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :ip_block, :create?
|
||||
|
||||
@ip_block = IpBlock.new(resource_params)
|
||||
|
||||
if @ip_block.save
|
||||
log_action :create, @ip_block
|
||||
redirect_to admin_ip_blocks_path, notice: I18n.t('admin.ip_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.ip_blocks.no_ip_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
ensure
|
||||
redirect_to admin_ip_blocks_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
'delete' if params[:delete]
|
||||
end
|
||||
|
||||
def form_ip_block_batch_params
|
||||
params.require(:form_ip_block_batch).permit(ip_block_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
@ -1,38 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Monkey-patch on monkey-patch.
|
||||
# Because it conflicts with the request.rb patch.
|
||||
class HTTP::Timeout::PerOperationOriginal < HTTP::Timeout::PerOperation
|
||||
def connect(socket_class, host, port, nodelay = false)
|
||||
::Timeout.timeout(@connect_timeout, HTTP::TimeoutError) do
|
||||
@socket = socket_class.open(host, port)
|
||||
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module WebfingerHelper
|
||||
def webfinger!(uri)
|
||||
hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
||||
|
||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri
|
||||
|
||||
opts = {
|
||||
ssl: !hidden_service_uri,
|
||||
|
||||
headers: {
|
||||
'User-Agent': Mastodon::Version.user_agent,
|
||||
},
|
||||
|
||||
timeout_class: HTTP::Timeout::PerOperationOriginal,
|
||||
|
||||
timeout_options: {
|
||||
write_timeout: 10,
|
||||
connect_timeout: 5,
|
||||
read_timeout: 10,
|
||||
},
|
||||
}
|
||||
|
||||
Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger
|
||||
Webfinger.new(uri).perform
|
||||
end
|
||||
end
|
||||
|
@ -1,2 +1,3 @@
|
||||
import 'packs/public-path';
|
||||
import './settings';
|
||||
import './two_factor_authentication';
|
||||
|
@ -1,5 +1,6 @@
|
||||
// This file will be loaded on all pages, regardless of theme.
|
||||
|
||||
import 'packs/public-path';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
require.context('../images/', true);
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default @connect(() => {})
|
||||
class NotificationsPermissionBanner extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.dispatch(requestBrowserPermission());
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='notifications-permission-banner'>
|
||||
<h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2>
|
||||
<p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' /> }} /></p>
|
||||
<Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import ready from './ready';
|
||||
|
||||
export let assetHost = '';
|
||||
|
||||
ready(() => {
|
||||
const cdnHost = document.querySelector('meta[name=cdn-host]');
|
||||
if (cdnHost) {
|
||||
assetHost = cdnHost.content || '';
|
||||
}
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
// Handles browser quirks, based on
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
|
||||
|
||||
const checkNotificationPromise = () => {
|
||||
try {
|
||||
Notification.requestPermission().then();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlePermission = (permission, callback) => {
|
||||
// Whatever the user answers, we make sure Chrome stores the information
|
||||
if(!('permission' in Notification)) {
|
||||
Notification.permission = permission;
|
||||
}
|
||||
|
||||
callback(Notification.permission);
|
||||
};
|
||||
|
||||
export const requestNotificationPermission = (callback) => {
|
||||
if (checkNotificationPromise()) {
|
||||
Notification.requestPermission().then((permission) => handlePermission(permission, callback));
|
||||
} else {
|
||||
Notification.requestPermission((permission) => handlePermission(permission, callback));
|
||||
}
|
||||
};
|
@ -1,8 +1,21 @@
|
||||
import { changeSetting, saveSettings } from './settings';
|
||||
import { requestBrowserPermission } from './notifications';
|
||||
|
||||
export const INTRODUCTION_VERSION = 20181216044202;
|
||||
|
||||
export const closeOnboarding = () => dispatch => {
|
||||
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
|
||||
dispatch(saveSettings());
|
||||
|
||||
dispatch(requestBrowserPermission((permission) => {
|
||||
if (permission === 'granted') {
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'follow'], true));
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'favourite'], true));
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'reblog'], true));
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'mention'], true));
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'poll'], true));
|
||||
dispatch(changeSetting(['notifications', 'alerts', 'status'], true));
|
||||
dispatch(saveSettings());
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import Button from 'mastodon/components/button';
|
||||
import { requestBrowserPermission } from 'mastodon/actions/notifications';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default @connect(() => {})
|
||||
class NotificationsPermissionBanner extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.dispatch(requestBrowserPermission());
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='notifications-permission-banner'>
|
||||
<h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2>
|
||||
<p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' /> }} /></p>
|
||||
<Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import ready from '../ready';
|
||||
|
||||
export let assetHost = '';
|
||||
|
||||
ready(() => {
|
||||
const cdnHost = document.querySelector('meta[name=cdn-host]');
|
||||
if (cdnHost) {
|
||||
assetHost = cdnHost.content || '';
|
||||
}
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
// Handles browser quirks, based on
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
|
||||
|
||||
const checkNotificationPromise = () => {
|
||||
try {
|
||||
Notification.requestPermission().then();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlePermission = (permission, callback) => {
|
||||
// Whatever the user answers, we make sure Chrome stores the information
|
||||
if(!('permission' in Notification)) {
|
||||
Notification.permission = permission;
|
||||
}
|
||||
|
||||
callback(Notification.permission);
|
||||
};
|
||||
|
||||
export const requestNotificationPermission = (callback) => {
|
||||
if (checkNotificationPromise()) {
|
||||
Notification.requestPermission().then((permission) => handlePermission(permission, callback));
|
||||
} else {
|
||||
Notification.requestPermission((permission) => handlePermission(permission, callback));
|
||||
}
|
||||
};
|
@ -1 +1,2 @@
|
||||
import './public-path';
|
||||
import 'styles/application.scss';
|
||||
|
@ -0,0 +1,21 @@
|
||||
// Dynamically set webpack's loading path depending on a meta header, in order
|
||||
// to share the same assets regardless of instance configuration.
|
||||
// See https://webpack.js.org/guides/public-path/#on-the-fly
|
||||
|
||||
function removeOuterSlashes(string) {
|
||||
return string.replace(/^\/*/, '').replace(/\/*$/, '');
|
||||
}
|
||||
|
||||
function formatPublicPath(host = '', path = '') {
|
||||
let formattedHost = removeOuterSlashes(host);
|
||||
if (formattedHost && !/^http/i.test(formattedHost)) {
|
||||
formattedHost = `//${formattedHost}`;
|
||||
}
|
||||
const formattedPath = removeOuterSlashes(path);
|
||||
return `${formattedHost}/${formattedPath}/`;
|
||||
}
|
||||
|
||||
const cdnHost = document.querySelector('meta[name=cdn-host]');
|
||||
|
||||
// eslint-disable-next-line camelcase, no-undef, no-unused-vars
|
||||
__webpack_public_path__ = formatPublicPath(cdnHost ? cdnHost.content : '', process.env.PUBLIC_OUTPUT_PATH);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue