mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-29 00:31:37 +01:00
Merge commit '49b3d5692e6f217e6506674ad8a623a4ba8d0c5f' into glitch-soc/backports-4.3
This commit is contained in:
commit
b091e531a5
10 changed files with 90 additions and 93 deletions
|
@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
||||||
|
|
||||||
const handleLeftNav = useCallback(() => {
|
const handleLeftNav = useCallback(() => {
|
||||||
|
@ -146,8 +151,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
import { replyCompose } from 'mastodon/actions/compose';
|
import { replyCompose } from 'mastodon/actions/compose';
|
||||||
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
@ -159,22 +161,26 @@ class Footer extends ImmutablePureComponent {
|
||||||
replyTitle = intl.formatMessage(messages.replyAll);
|
replyTitle = intl.formatMessage(messages.replyAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reblogTitle = '';
|
let reblogTitle, reblogIconComponent;
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
|
reblogIconComponent = RepeatIcon;
|
||||||
} else if (reblogPrivate) {
|
} else if (reblogPrivate) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||||
|
reblogIconComponent = RepeatPrivateIcon;
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||||
|
reblogIconComponent = RepeatDisabledIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='picture-in-picture__footer'>
|
<div className='picture-in-picture__footer'>
|
||||||
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||||
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />}
|
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,6 +35,10 @@ body.rtl {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-back-button__icon {
|
||||||
|
transform: scale(-1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.simple_form select {
|
.simple_form select {
|
||||||
background: $ui-base-color
|
background: $ui-base-color
|
||||||
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>")
|
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>")
|
||||||
|
|
|
@ -40,7 +40,7 @@ class ContentSecurityPolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
def cdn_host_value
|
def cdn_host_value
|
||||||
s3_alias_host || s3_cloudfront_host || azure_alias_host || s3_hostname_host
|
s3_alias_host || s3_cloudfront_host || azure_alias_host || s3_hostname_host || swift_object_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def paperclip_root_url
|
def paperclip_root_url
|
||||||
|
@ -76,6 +76,14 @@ class ContentSecurityPolicy
|
||||||
host_to_url ENV.fetch('S3_HOSTNAME', nil)
|
host_to_url ENV.fetch('S3_HOSTNAME', nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def swift_object_url
|
||||||
|
url = ENV.fetch('SWIFT_OBJECT_URL', nil)
|
||||||
|
return if url.blank? || !url.start_with?('https://')
|
||||||
|
|
||||||
|
url += '/' unless url.end_with?('/')
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
def uri_from_configuration_and_string(host_string)
|
def uri_from_configuration_and_string(host_string)
|
||||||
Addressable::URI.parse("#{host_protocol}://#{host_string}").tap do |uri|
|
Addressable::URI.parse("#{host_protocol}://#{host_string}").tap do |uri|
|
||||||
uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/')
|
uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/')
|
||||||
|
|
|
@ -18,5 +18,6 @@ class FollowRecommendation < ApplicationRecord
|
||||||
belongs_to :account_summary, foreign_key: :account_id, inverse_of: false
|
belongs_to :account_summary, foreign_key: :account_id, inverse_of: false
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
scope :localized, ->(locale) { joins(:account_summary).merge(AccountSummary.localized(locale)) }
|
scope :unsupressed, -> { where.not(FollowRecommendationSuppression.where(FollowRecommendationSuppression.arel_table[:account_id].eq(arel_table[:account_id])).select(1).arel.exists) }
|
||||||
|
scope :localized, ->(locale) { unsupressed.joins(:account_summary).merge(AccountSummary.localized(locale)) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||||
|
|
||||||
Run `bin/rails db:encryption:init` to generate new secrets and then assign the environment variables.
|
Run `bin/rails db:encryption:init` to generate new secrets and then assign the environment variables.
|
||||||
|
Do not change the secrets once they are set, as doing so may cause data loss and other issues that will be difficult or impossible to recover from.
|
||||||
MESSAGE
|
MESSAGE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,17 @@ namespace :db do
|
||||||
namespace :encryption do
|
namespace :encryption do
|
||||||
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
|
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
|
||||||
task :init do # rubocop:disable Rails/RakeEnvironment
|
task :init do # rubocop:disable Rails/RakeEnvironment
|
||||||
|
if %w(
|
||||||
|
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
|
||||||
|
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||||
|
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||||
|
).any? { |key| ENV.key?(key) }
|
||||||
|
pastel = Pastel.new
|
||||||
|
puts pastel.red(<<~MSG)
|
||||||
|
WARNING: It looks like encryption secrets have already been set. Please ensure you are not changing secrets for a Mastodon installation that already uses them, as this will cause data loss and other issues that are difficult to recover from.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
|
||||||
puts <<~MSG
|
puts <<~MSG
|
||||||
Add the following secret environment variables to your Mastodon environment (e.g. .env.production), ensure they are shared across all your nodes and do not change them after they are set:#{' '}
|
Add the following secret environment variables to your Mastodon environment (e.g. .env.production), ensure they are shared across all your nodes and do not change them after they are set:#{' '}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::TagsController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
before do
|
|
||||||
Fabricate(:tag)
|
|
||||||
|
|
||||||
tag_filter = instance_double(Admin::TagFilter, results: Tag.all)
|
|
||||||
allow(Admin::TagFilter).to receive(:new).and_return(tag_filter)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) { { order: 'newest' } }
|
|
||||||
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
expect(response).to render_template(:index)
|
|
||||||
|
|
||||||
expect(Admin::TagFilter)
|
|
||||||
.to have_received(:new)
|
|
||||||
.with(hash_including(params))
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'with filters' do
|
|
||||||
let(:params) { { order: 'newest', name: 'test' } }
|
|
||||||
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index, params: { name: 'test' }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
expect(response).to render_template(:index)
|
|
||||||
|
|
||||||
expect(Admin::TagFilter)
|
|
||||||
.to have_received(:new)
|
|
||||||
.with(hash_including(params))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #show' do
|
|
||||||
let!(:tag) { Fabricate(:tag) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
get :show, params: { id: tag.id }
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns status 200' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'PUT #update' do
|
|
||||||
let!(:tag) { Fabricate(:tag, listable: false) }
|
|
||||||
|
|
||||||
context 'with valid params' do
|
|
||||||
it 'updates the tag' do
|
|
||||||
put :update, params: { id: tag.id, tag: { listable: '1' } }
|
|
||||||
|
|
||||||
expect(response).to redirect_to(admin_tag_path(tag.id))
|
|
||||||
expect(tag.reload).to be_listable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with invalid params' do
|
|
||||||
it 'does not update the tag' do
|
|
||||||
put :update, params: { id: tag.id, tag: { name: 'cant-change-name' } }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
expect(response).to render_template(:show)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
38
spec/system/admin/tags_spec.rb
Normal file
38
spec/system/admin/tags_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Admin Tags' do
|
||||||
|
describe 'Tag interaction' do
|
||||||
|
let!(:tag) { Fabricate(:tag, name: 'test') }
|
||||||
|
|
||||||
|
before { sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||||
|
|
||||||
|
it 'allows tags listing and editing' do
|
||||||
|
visit admin_tags_path
|
||||||
|
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.tags.title'))
|
||||||
|
|
||||||
|
click_on '#test'
|
||||||
|
|
||||||
|
fill_in display_name_field, with: 'NewTagName'
|
||||||
|
expect { click_on submit_button }
|
||||||
|
.to_not(change { tag.reload.display_name })
|
||||||
|
expect(page)
|
||||||
|
.to have_content(match_error_text)
|
||||||
|
|
||||||
|
fill_in display_name_field, with: 'TEST'
|
||||||
|
expect { click_on submit_button }
|
||||||
|
.to(change { tag.reload.display_name }.to('TEST'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_name_field
|
||||||
|
I18n.t('simple_form.labels.defaults.display_name')
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_error_text
|
||||||
|
I18n.t('tags.does_not_match_previous_name')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -50,9 +50,9 @@ function getSentinelConfiguration(env, commonOptions) {
|
||||||
return {
|
return {
|
||||||
db: redisDatabase,
|
db: redisDatabase,
|
||||||
name: env.REDIS_SENTINEL_MASTER,
|
name: env.REDIS_SENTINEL_MASTER,
|
||||||
username: env.REDIS_USERNAME,
|
username: env.REDIS_USER,
|
||||||
password: env.REDIS_PASSWORD,
|
password: env.REDIS_PASSWORD,
|
||||||
sentinelUsername: env.REDIS_SENTINEL_USERNAME ?? env.REDIS_USERNAME,
|
sentinelUsername: env.REDIS_SENTINEL_USERNAME ?? env.REDIS_USER,
|
||||||
sentinelPassword: env.REDIS_SENTINEL_PASSWORD ?? env.REDIS_PASSWORD,
|
sentinelPassword: env.REDIS_SENTINEL_PASSWORD ?? env.REDIS_PASSWORD,
|
||||||
sentinels,
|
sentinels,
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
|
@ -104,7 +104,7 @@ export function configFromEnv(env) {
|
||||||
host: env.REDIS_HOST ?? '127.0.0.1',
|
host: env.REDIS_HOST ?? '127.0.0.1',
|
||||||
port: redisPort,
|
port: redisPort,
|
||||||
db: redisDatabase,
|
db: redisDatabase,
|
||||||
username: env.REDIS_USERNAME,
|
username: env.REDIS_USER,
|
||||||
password: env.REDIS_PASSWORD,
|
password: env.REDIS_PASSWORD,
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue