mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-15 03:15:32 +00:00
Merge pull request #490 from glitch-soc/merge-upstream
Merge with tootsuite @ 57b503d4ef
This commit is contained in:
commit
625c4f36ef
|
@ -37,7 +37,7 @@ addons:
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.4.3
|
- 2.4.3
|
||||||
- 2.5.0
|
- 2.5.1
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- redis-server
|
- redis-server
|
||||||
|
@ -47,6 +47,10 @@ install:
|
||||||
- bundle install --path=vendor/bundle --with pam_authentication --without development production --retry=3 --jobs=16
|
- bundle install --path=vendor/bundle --with pam_authentication --without development production --retry=3 --jobs=16
|
||||||
- yarn install
|
- yarn install
|
||||||
|
|
||||||
|
# https://github.com/travis-ci/travis-ci/issues/9333
|
||||||
|
before_install:
|
||||||
|
- gem install bundler
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- travis_wait ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
|
- travis_wait ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_accounts
|
def load_accounts
|
||||||
|
return [] if @account.user_hides_network? && current_account.id != @account.id
|
||||||
|
|
||||||
default_accounts.merge(paginated_follows).to_a
|
default_accounts.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_accounts
|
def load_accounts
|
||||||
|
return [] if @account.user_hides_network? && current_account.id != @account.id
|
||||||
|
|
||||||
default_accounts.merge(paginated_follows).to_a
|
default_accounts.merge(paginated_follows).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,15 @@ class FollowerAccountsController < ApplicationController
|
||||||
format.html do
|
format.html do
|
||||||
use_pack 'public'
|
use_pack 'public'
|
||||||
|
|
||||||
|
next if @account.user_hides_network?
|
||||||
|
|
||||||
follows
|
follows
|
||||||
@relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in?
|
@relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
|
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||||
|
|
||||||
render json: collection_presenter,
|
render json: collection_presenter,
|
||||||
serializer: ActivityPub::CollectionSerializer,
|
serializer: ActivityPub::CollectionSerializer,
|
||||||
adapter: ActivityPub::Adapter,
|
adapter: ActivityPub::Adapter,
|
||||||
|
|
|
@ -8,11 +8,15 @@ class FollowingAccountsController < ApplicationController
|
||||||
format.html do
|
format.html do
|
||||||
use_pack 'public'
|
use_pack 'public'
|
||||||
|
|
||||||
|
next if @account.user_hides_network?
|
||||||
|
|
||||||
follows
|
follows
|
||||||
@relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
|
@relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
|
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||||
|
|
||||||
render json: collection_presenter,
|
render json: collection_presenter,
|
||||||
serializer: ActivityPub::CollectionSerializer,
|
serializer: ActivityPub::CollectionSerializer,
|
||||||
adapter: ActivityPub::Adapter,
|
adapter: ActivityPub::Adapter,
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Settings::PreferencesController < Settings::BaseController
|
||||||
:setting_reduce_motion,
|
:setting_reduce_motion,
|
||||||
:setting_system_font_ui,
|
:setting_system_font_ui,
|
||||||
:setting_noindex,
|
:setting_noindex,
|
||||||
|
:setting_hide_network,
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following)
|
||||||
)
|
)
|
||||||
|
|
|
@ -76,9 +76,14 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||||
|
|
||||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||||
|
|
||||||
export function expandNotifications({ maxId } = {}) {
|
const noOp = () => {};
|
||||||
|
|
||||||
|
export function expandNotifications({ maxId } = {}, done = noOp) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (getState().getIn(['notifications', 'isLoading'])) {
|
const notifications = getState().get('notifications');
|
||||||
|
|
||||||
|
if (notifications.get('isLoading')) {
|
||||||
|
done();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +92,10 @@ export function expandNotifications({ maxId } = {}) {
|
||||||
exclude_types: excludeTypesFromSettings(getState()),
|
exclude_types: excludeTypesFromSettings(getState()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!maxId && notifications.get('items').size > 0) {
|
||||||
|
params.since_id = notifications.getIn(['items', 0]);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(expandNotificationsRequest());
|
dispatch(expandNotificationsRequest());
|
||||||
|
|
||||||
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
||||||
|
@ -97,8 +106,10 @@ export function expandNotifications({ maxId } = {}) {
|
||||||
|
|
||||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
||||||
fetchRelatedRelationships(dispatch, response.data);
|
fetchRelatedRelationships(dispatch, response.data);
|
||||||
|
done();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(expandNotificationsFail(error));
|
dispatch(expandNotificationsFail(error));
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,10 +36,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshHomeTimelineAndNotification (dispatch) {
|
const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||||
dispatch(expandHomeTimeline());
|
dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
|
||||||
dispatch(expandNotifications());
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
||||||
export const connectCommunityStream = () => connectTimelineStream('community', 'public:local');
|
export const connectCommunityStream = () => connectTimelineStream('community', 'public:local');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
||||||
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
||||||
|
@ -64,35 +64,44 @@ export function deleteFromTimelines(id) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function expandTimeline(timelineId, path, params = {}) {
|
const noOp = () => {};
|
||||||
|
|
||||||
|
export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
|
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
|
||||||
|
|
||||||
if (timeline.get('isLoading')) {
|
if (timeline.get('isLoading')) {
|
||||||
|
done();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!params.max_id && timeline.get('items', ImmutableList()).size > 0) {
|
||||||
|
params.since_id = timeline.getIn(['items', 0]);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(expandTimelineRequest(timelineId));
|
dispatch(expandTimelineRequest(timelineId));
|
||||||
|
|
||||||
api(getState).get(path, { params }).then(response => {
|
api(getState).get(path, { params }).then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedStatuses(response.data));
|
dispatch(importFetchedStatuses(response.data));
|
||||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206));
|
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206));
|
||||||
|
done();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(expandTimelineFail(timelineId, error));
|
dispatch(expandTimelineFail(timelineId, error));
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandHomeTimeline = ({ maxId } = {}) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId });
|
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||||
export const expandPublicTimeline = ({ maxId } = {}) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId });
|
export const expandPublicTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }, done);
|
||||||
export const expandCommunityTimeline = ({ maxId } = {}) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId });
|
export const expandCommunityTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }, done);
|
||||||
export const expandDirectTimeline = ({ maxId } = {}) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId });
|
export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
|
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
|
||||||
export const expandHashtagTimeline = (hashtag, { maxId } = {}) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId });
|
export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done);
|
||||||
export const expandListTimeline = (id, { maxId } = {}) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId });
|
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||||
|
|
||||||
export function expandTimelineRequest(timeline) {
|
export function expandTimelineRequest(timeline) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
import WebSocketClient from 'websocket.js';
|
import WebSocketClient from 'websocket.js';
|
||||||
|
|
||||||
|
const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
|
||||||
|
|
||||||
export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) {
|
export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
|
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
|
||||||
const accessToken = getState().getIn(['meta', 'access_token']);
|
const accessToken = getState().getIn(['meta', 'access_token']);
|
||||||
const { onDisconnect, onReceive } = callbacks(dispatch, getState);
|
const { onDisconnect, onReceive } = callbacks(dispatch, getState);
|
||||||
|
|
||||||
let polling = null;
|
let polling = null;
|
||||||
|
|
||||||
const setupPolling = () => {
|
const setupPolling = () => {
|
||||||
polling = setInterval(() => {
|
pollingRefresh(dispatch, () => {
|
||||||
pollingRefresh(dispatch);
|
polling = setTimeout(() => setupPolling(), 20000 + randomIntUpTo(20000));
|
||||||
}, 20000);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearPolling = () => {
|
const clearPolling = () => {
|
||||||
if (polling) {
|
if (polling) {
|
||||||
clearInterval(polling);
|
clearTimeout(polling);
|
||||||
polling = null;
|
polling = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -29,8 +32,9 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
|
||||||
|
|
||||||
disconnected () {
|
disconnected () {
|
||||||
if (pollingRefresh) {
|
if (pollingRefresh) {
|
||||||
setupPolling();
|
polling = setTimeout(() => setupPolling(), randomIntUpTo(40000));
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisconnect();
|
onDisconnect();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -51,6 +55,7 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
subscription.close();
|
subscription.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPolling();
|
clearPolling();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -322,6 +322,15 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&.empty img {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.2;
|
||||||
|
height: 200px;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 740px) {
|
@media screen and (max-width: 740px) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -438,8 +447,8 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px 0;
|
padding: 130px 0;
|
||||||
padding-top: 55px;
|
padding-top: 125px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
|
|
||||||
.domain {
|
.footer__domain {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -118,4 +118,13 @@ class ActivityPub::Activity
|
||||||
def delete_later!(uri)
|
def delete_later!(uri)
|
||||||
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
|
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_remote_original_status
|
||||||
|
if object_uri.start_with?('http')
|
||||||
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
||||||
|
elsif @object['url'].present?
|
||||||
|
::FetchRemoteStatusService.new.call(@object['url'])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,10 @@ class ActivityPub::Activity::Add < ActivityPub::Activity
|
||||||
def perform
|
def perform
|
||||||
return unless @json['target'].present? && value_or_id(@json['target']) == @account.featured_collection_url
|
return unless @json['target'].present? && value_or_id(@json['target']) == @account.featured_collection_url
|
||||||
|
|
||||||
status = status_from_uri(object_uri)
|
status = status_from_uri(object_uri)
|
||||||
|
status ||= fetch_remote_original_status
|
||||||
|
|
||||||
return unless status.account_id == @account.id && !@account.pinned?(status)
|
return unless !status.nil? && status.account_id == @account.id && !@account.pinned?(status)
|
||||||
|
|
||||||
StatusPin.create!(account: @account, status: status)
|
StatusPin.create!(account: @account, status: status)
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,16 +26,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def fetch_remote_original_status
|
|
||||||
if object_uri.start_with?('http')
|
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
|
||||||
|
|
||||||
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
|
|
||||||
elsif @object['url'].present?
|
|
||||||
::FetchRemoteStatusService.new.call(@object['url'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def announceable?(status)
|
def announceable?(status)
|
||||||
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
|
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::Activity::Remove < ActivityPub::Activity
|
||||||
|
|
||||||
status = status_from_uri(object_uri)
|
status = status_from_uri(object_uri)
|
||||||
|
|
||||||
return unless status.account_id == @account.id
|
return unless !status.nil? && status.account_id == @account.id
|
||||||
|
|
||||||
pin = StatusPin.find_by(account: @account, status: status)
|
pin = StatusPin.find_by(account: @account, status: status)
|
||||||
pin&.destroy!
|
pin&.destroy!
|
||||||
|
|
|
@ -58,7 +58,7 @@ class Request
|
||||||
|
|
||||||
def set_common_headers!
|
def set_common_headers!
|
||||||
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
|
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
|
||||||
@headers['User-Agent'] = user_agent
|
@headers['User-Agent'] = Mastodon::Version.user_agent
|
||||||
@headers['Host'] = @url.host
|
@headers['Host'] = @url.host
|
||||||
@headers['Date'] = Time.now.utc.httpdate
|
@headers['Date'] = Time.now.utc.httpdate
|
||||||
@headers['Accept-Encoding'] = 'gzip' if @verb != :head
|
@headers['Accept-Encoding'] = 'gzip' if @verb != :head
|
||||||
|
@ -83,10 +83,6 @@ class Request
|
||||||
@headers.keys.join(' ').downcase
|
@headers.keys.join(' ').downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_agent
|
|
||||||
@user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Mastodon::Version}; +#{root_url})"
|
|
||||||
end
|
|
||||||
|
|
||||||
def key_id
|
def key_id
|
||||||
case @key_id_format
|
case @key_id_format
|
||||||
when :acct
|
when :acct
|
||||||
|
|
|
@ -30,6 +30,7 @@ class UserSettingsDecorator
|
||||||
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
||||||
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
|
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
|
||||||
user.settings['skin'] = skin_preference if change?('setting_skin')
|
user.settings['skin'] = skin_preference if change?('setting_skin')
|
||||||
|
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
||||||
end
|
end
|
||||||
|
|
||||||
def merged_notification_emails
|
def merged_notification_emails
|
||||||
|
@ -92,6 +93,10 @@ class UserSettingsDecorator
|
||||||
settings['setting_skin']
|
settings['setting_skin']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hide_network_preference
|
||||||
|
boolean_cast_setting 'setting_hide_network'
|
||||||
|
end
|
||||||
|
|
||||||
def boolean_cast_setting(key)
|
def boolean_cast_setting(key)
|
||||||
ActiveModel::Type::Boolean.new.cast(settings[key])
|
ActiveModel::Type::Boolean.new.cast(settings[key])
|
||||||
end
|
end
|
||||||
|
|
|
@ -139,6 +139,7 @@ class Account < ApplicationRecord
|
||||||
:moderator?,
|
:moderator?,
|
||||||
:staff?,
|
:staff?,
|
||||||
:locale,
|
:locale,
|
||||||
|
:hides_network?,
|
||||||
to: :user,
|
to: :user,
|
||||||
prefix: true,
|
prefix: true,
|
||||||
allow_nil: true
|
allow_nil: true
|
||||||
|
|
|
@ -86,7 +86,7 @@ class User < ApplicationRecord
|
||||||
has_many :session_activations, dependent: :destroy
|
has_many :session_activations, dependent: :destroy
|
||||||
|
|
||||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
|
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
|
||||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_sensitive_media,
|
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_sensitive_media, :hide_network,
|
||||||
to: :settings, prefix: :setting, allow_nil: false
|
to: :settings, prefix: :setting, allow_nil: false
|
||||||
|
|
||||||
attr_accessor :invite_code
|
attr_accessor :invite_code
|
||||||
|
@ -219,6 +219,10 @@ class User < ApplicationRecord
|
||||||
settings.notification_emails['digest']
|
settings.notification_emails['digest']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hides_network?
|
||||||
|
@hides_network ||= settings.hide_network
|
||||||
|
end
|
||||||
|
|
||||||
def token_for_app(a)
|
def token_for_app(a)
|
||||||
return nil if a.nil? || a.owner != self
|
return nil if a.nil? || a.owner != self
|
||||||
Doorkeeper::AccessToken
|
Doorkeeper::AccessToken
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.accounts-grid
|
.accounts-grid{ class: accounts.empty? ? 'empty' : '' }
|
||||||
- if accounts.empty?
|
- if accounts.empty?
|
||||||
|
= image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational'
|
||||||
= render partial: 'accounts/nothing_here'
|
= render partial: 'accounts/nothing_here'
|
||||||
- else
|
- else
|
||||||
= render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: !user_signed_in?
|
= render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: !user_signed_in?
|
||||||
|
|
3
app/views/accounts/_follow_grid_hidden.html.haml
Normal file
3
app/views/accounts/_follow_grid_hidden.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.accounts-grid.empty
|
||||||
|
= image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational'
|
||||||
|
%p.nothing-here= t('accounts.network_hidden')
|
|
@ -7,4 +7,7 @@
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/header', account: @account
|
||||||
|
|
||||||
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:account)
|
- if @account.user_hides_network?
|
||||||
|
= render 'accounts/follow_grid_hidden'
|
||||||
|
- else
|
||||||
|
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:account)
|
||||||
|
|
|
@ -7,4 +7,7 @@
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/header', account: @account
|
||||||
|
|
||||||
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:target_account)
|
- if @account.user_hides_network?
|
||||||
|
= render 'accounts/follow_grid_hidden'
|
||||||
|
- else
|
||||||
|
= render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:target_account)
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
%span.single-user-login
|
%span.single-user-login
|
||||||
= link_to t('auth.login'), new_user_session_path
|
= link_to t('auth.login'), new_user_session_path
|
||||||
—
|
—
|
||||||
%span.domain= link_to site_hostname, about_path
|
%span.footer__domain= link_to site_hostname, about_path
|
||||||
- else
|
- else
|
||||||
%span.domain= link_to site_hostname, root_path
|
%span.footer__domain= link_to site_hostname, root_path
|
||||||
%span.powered-by
|
%span.powered-by
|
||||||
!= t('generic.powered_by', link: link_to('Mastodon', 'https://joinmastodon.org'))
|
!= t('generic.powered_by', link: link_to('Mastodon', 'https://joinmastodon.org'))
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
|
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :setting_hide_network, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
%h4= t 'preferences.web'
|
%h4= t 'preferences.web'
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
|
|
|
@ -18,7 +18,8 @@ module Goldfinger
|
||||||
def self.finger(uri, opts = {})
|
def self.finger(uri, opts = {})
|
||||||
to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
||||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden
|
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden
|
||||||
opts = opts.merge(Rails.configuration.x.http_client_proxy).merge(ssl: !to_hidden)
|
opts = { ssl: !to_hidden, headers: {} }.merge(Rails.configuration.x.http_client_proxy).merge(opts)
|
||||||
|
opts[:headers]['User-Agent'] ||= Mastodon::Version.user_agent
|
||||||
Goldfinger::Client.new(uri, opts).finger
|
Goldfinger::Client.new(uri, opts).finger
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,6 +40,7 @@ en:
|
||||||
following: Following
|
following: Following
|
||||||
media: Media
|
media: Media
|
||||||
moved_html: "%{name} has moved to %{new_profile_link}:"
|
moved_html: "%{name} has moved to %{new_profile_link}:"
|
||||||
|
network_hidden: This information is not available
|
||||||
nothing_here: There is nothing here!
|
nothing_here: There is nothing here!
|
||||||
people_followed_by: People whom %{name} follows
|
people_followed_by: People whom %{name} follows
|
||||||
people_who_follow: People who follow %{name}
|
people_who_follow: People who follow %{name}
|
||||||
|
|
|
@ -15,6 +15,7 @@ en:
|
||||||
note:
|
note:
|
||||||
one: <span class="note-counter">1</span> character left
|
one: <span class="note-counter">1</span> character left
|
||||||
other: <span class="note-counter">%{count}</span> characters left
|
other: <span class="note-counter">%{count}</span> characters left
|
||||||
|
setting_hide_network: Who you follow and who follows you will not be shown on your profile
|
||||||
setting_noindex: Affects your public profile and status pages
|
setting_noindex: Affects your public profile and status pages
|
||||||
setting_skin: Reskins the selected Mastodon flavour
|
setting_skin: Reskins the selected Mastodon flavour
|
||||||
imports:
|
imports:
|
||||||
|
@ -55,6 +56,7 @@ en:
|
||||||
setting_delete_modal: Show confirmation dialog before deleting a toot
|
setting_delete_modal: Show confirmation dialog before deleting a toot
|
||||||
setting_display_sensitive_media: Always show media marked as sensitive
|
setting_display_sensitive_media: Always show media marked as sensitive
|
||||||
setting_favourite_modal: Show confirmation dialog before favouriting
|
setting_favourite_modal: Show confirmation dialog before favouriting
|
||||||
|
setting_hide_network: Hide your network
|
||||||
setting_noindex: Opt-out of search engine indexing
|
setting_noindex: Opt-out of search engine indexing
|
||||||
setting_reduce_motion: Reduce motion in animations
|
setting_reduce_motion: Reduce motion in animations
|
||||||
setting_skin: Skin
|
setting_skin: Skin
|
||||||
|
|
|
@ -20,6 +20,7 @@ defaults: &defaults
|
||||||
min_invite_role: 'admin'
|
min_invite_role: 'admin'
|
||||||
show_staff_badge: true
|
show_staff_badge: true
|
||||||
default_sensitive: false
|
default_sensitive: false
|
||||||
|
hide_network: false
|
||||||
unfollow_modal: false
|
unfollow_modal: false
|
||||||
boost_modal: false
|
boost_modal: false
|
||||||
favourite_modal: false
|
favourite_modal: false
|
||||||
|
|
|
@ -48,5 +48,9 @@ module Mastodon
|
||||||
source_base_url
|
source_base_url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_agent
|
||||||
|
@user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Version}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,12 +18,31 @@ RSpec.describe ActivityPub::Activity::Add do
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
subject { described_class.new(json, sender) }
|
subject { described_class.new(json, sender) }
|
||||||
|
|
||||||
before do
|
it 'creates a pin' do
|
||||||
subject.perform
|
subject.perform
|
||||||
|
expect(sender.pinned?(status)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a pin' do
|
context 'when status was not known before' do
|
||||||
expect(sender.pinned?(status)).to be true
|
let(:json) do
|
||||||
|
{
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
id: 'foo',
|
||||||
|
type: 'Add',
|
||||||
|
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||||
|
object: 'https://example.com/unknown',
|
||||||
|
target: sender.featured_collection_url,
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/unknown').to_return(status: 410)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fetches the status' do
|
||||||
|
subject.perform
|
||||||
|
expect(a_request(:get, 'https://example.com/unknown')).to have_been_made.at_least_once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue