forked from mirrors/catstodon
merge catstodon/main into main
This commit is contained in:
commit
380567f453
47 changed files with 786 additions and 105 deletions
.github/workflows
Vagrantfileapp
controllers
javascript
flavours
glitch
vanilla
mastodon/features/ui/components
skins
glitch
vanilla
styles/mastodon
lib
models
services
workers/scheduler
config
db
post_migrate
schema.rblib
spec
controllers
lib
models
2
.github/workflows/check-i18n.yml
vendored
2
.github/workflows/check-i18n.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- name: Set up Ruby
|
- name: Set up Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.0'
|
ruby-version: .ruby-version
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
- name: Check locale file normalization
|
- name: Check locale file normalization
|
||||||
run: bundle exec i18n-tasks check-normalized
|
run: bundle exec i18n-tasks check-normalized
|
||||||
|
|
2
.github/workflows/linter.yml
vendored
2
.github/workflows/linter.yml
vendored
|
@ -53,7 +53,7 @@ jobs:
|
||||||
- name: Set-up Node.js
|
- name: Set-up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version-file: .nvmrc
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
71
Vagrantfile
vendored
71
Vagrantfile
vendored
|
@ -3,16 +3,14 @@
|
||||||
|
|
||||||
ENV["PORT"] ||= "3000"
|
ENV["PORT"] ||= "3000"
|
||||||
|
|
||||||
$provision = <<SCRIPT
|
$provisionA = <<SCRIPT
|
||||||
|
|
||||||
cd /vagrant # This is where the host folder/repo is mounted
|
|
||||||
|
|
||||||
# Add the yarn repo + yarn repo keys
|
# Add the yarn repo + yarn repo keys
|
||||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||||
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
|
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
|
||||||
|
|
||||||
# Add repo for NodeJS
|
# Add repo for NodeJS
|
||||||
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
|
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
|
||||||
|
|
||||||
# Add firewall rule to redirect 80 to PORT and save
|
# Add firewall rule to redirect 80 to PORT and save
|
||||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
|
||||||
|
@ -33,32 +31,56 @@ sudo apt-get install \
|
||||||
redis-tools \
|
redis-tools \
|
||||||
postgresql \
|
postgresql \
|
||||||
postgresql-contrib \
|
postgresql-contrib \
|
||||||
yarn \
|
|
||||||
libicu-dev \
|
libicu-dev \
|
||||||
libidn11-dev \
|
libidn11-dev \
|
||||||
libreadline-dev \
|
libreadline6-dev \
|
||||||
libpam0g-dev \
|
autoconf \
|
||||||
|
bison \
|
||||||
|
build-essential \
|
||||||
|
ffmpeg \
|
||||||
|
file \
|
||||||
|
gcc \
|
||||||
|
libffi-dev \
|
||||||
|
libgdbm-dev \
|
||||||
|
libjemalloc-dev \
|
||||||
|
libncurses5-dev \
|
||||||
|
libprotobuf-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libyaml-dev \
|
||||||
|
pkg-config \
|
||||||
|
protobuf-compiler \
|
||||||
|
zlib1g-dev \
|
||||||
-y
|
-y
|
||||||
|
|
||||||
# Install rvm
|
# Install rvm
|
||||||
read RUBY_VERSION < .ruby-version
|
sudo apt-add-repository -y ppa:rael-gc/rvm
|
||||||
|
sudo apt-get install rvm -y
|
||||||
|
|
||||||
curl -sSL https://rvm.io/mpapis.asc | gpg --import
|
sudo usermod -a -G rvm $USER
|
||||||
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
|
|
||||||
|
|
||||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
SCRIPT
|
||||||
source /home/vagrant/.rvm/scripts/rvm
|
|
||||||
|
$provisionB = <<SCRIPT
|
||||||
|
|
||||||
|
source "/etc/profile.d/rvm.sh"
|
||||||
|
|
||||||
# Install Ruby
|
# Install Ruby
|
||||||
rvm reinstall ruby-$RUBY_VERSION --disable-binary
|
read RUBY_VERSION < /vagrant/.ruby-version
|
||||||
|
rvm install ruby-$RUBY_VERSION --disable-binary
|
||||||
|
|
||||||
# Configure database
|
# Configure database
|
||||||
sudo -u postgres createuser -U postgres vagrant -s
|
sudo -u postgres createuser -U postgres vagrant -s
|
||||||
sudo -u postgres createdb -U postgres mastodon_development
|
sudo -u postgres createdb -U postgres mastodon_development
|
||||||
|
|
||||||
# Install gems and node modules
|
cd /vagrant # This is where the host folder/repo is mounted
|
||||||
|
|
||||||
|
# Install gems
|
||||||
gem install bundler foreman
|
gem install bundler foreman
|
||||||
bundle install
|
bundle install
|
||||||
|
|
||||||
|
# Install node modules
|
||||||
|
sudo corepack enable
|
||||||
|
yarn set version classic
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# Build Mastodon
|
# Build Mastodon
|
||||||
|
@ -72,18 +94,11 @@ echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
|
||||||
|
|
||||||
SCRIPT
|
SCRIPT
|
||||||
|
|
||||||
$start = <<SCRIPT
|
|
||||||
|
|
||||||
echo 'To start server'
|
|
||||||
echo ' $ vagrant ssh -c "cd /vagrant && foreman start"'
|
|
||||||
|
|
||||||
SCRIPT
|
|
||||||
|
|
||||||
VAGRANTFILE_API_VERSION = "2"
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
|
||||||
config.vm.box = "ubuntu/bionic64"
|
config.vm.box = "ubuntu/focal64"
|
||||||
|
|
||||||
config.vm.provider :virtualbox do |vb|
|
config.vm.provider :virtualbox do |vb|
|
||||||
vb.name = "mastodon"
|
vb.name = "mastodon"
|
||||||
|
@ -100,7 +115,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
# Use "virtio" network interfaces for better performance.
|
# Use "virtio" network interfaces for better performance.
|
||||||
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
|
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
|
||||||
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
|
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This uses the vagrant-hostsupdater plugin, and lets you
|
# This uses the vagrant-hostsupdater plugin, and lets you
|
||||||
|
@ -118,7 +132,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.vm.networks.any? { |type, options| type == :private_network }
|
if config.vm.networks.any? { |type, options| type == :private_network }
|
||||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp', 'actimeo=1']
|
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
|
||||||
else
|
else
|
||||||
config.vm.synced_folder ".", "/vagrant"
|
config.vm.synced_folder ".", "/vagrant"
|
||||||
end
|
end
|
||||||
|
@ -129,9 +143,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
config.vm.network :forwarded_port, guest: 8080, host: 8080
|
config.vm.network :forwarded_port, guest: 8080, host: 8080
|
||||||
|
|
||||||
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
|
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
|
||||||
config.vm.provision :shell, inline: $provision, privileged: false
|
config.vm.provision :shell, inline: $provisionA, privileged: false, reset: true
|
||||||
|
config.vm.provision :shell, inline: $provisionB, privileged: false
|
||||||
|
|
||||||
# Start up script, runs on every 'vagrant up'
|
config.vm.post_up_message = <<MESSAGE
|
||||||
config.vm.provision :shell, inline: $start, run: 'always', privileged: false
|
To start server
|
||||||
|
$ vagrant ssh -c "cd /vagrant && foreman start"
|
||||||
|
MESSAGE
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,12 +55,14 @@ module Admin
|
||||||
def approve
|
def approve
|
||||||
authorize @account.user, :approve?
|
authorize @account.user, :approve?
|
||||||
@account.user.approve!
|
@account.user.approve!
|
||||||
|
log_action :approve, @account.user
|
||||||
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
authorize @account.user, :reject?
|
authorize @account.user, :reject?
|
||||||
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||||
|
log_action :reject, @account.user
|
||||||
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
|
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -54,12 +54,14 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
def approve
|
def approve
|
||||||
authorize @account.user, :approve?
|
authorize @account.user, :approve?
|
||||||
@account.user.approve!
|
@account.user.approve!
|
||||||
|
log_action :approve, @account.user
|
||||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
authorize @account.user, :reject?
|
authorize @account.user, :reject?
|
||||||
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||||
|
log_action :reject, @account.user
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Api::V1::FiltersController < Api::BaseController
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
filter_category = current_account.custom_filters.create!(resource_params)
|
filter_category = current_account.custom_filters.create!(filter_params)
|
||||||
@filter = filter_category.keywords.create!(keyword_params)
|
@filter = filter_category.keywords.create!(keyword_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.permit(:phrase, :expires_in, :irreversible, context: [])
|
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
resource_params.slice(:expires_in, :irreversible, :context)
|
resource_params.slice(:phrase, :expires_in, :irreversible, :context)
|
||||||
end
|
end
|
||||||
|
|
||||||
def keyword_params
|
def keyword_params
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Avatar from 'flavours/glitch/components/avatar';
|
||||||
import Permalink from 'flavours/glitch/components/permalink';
|
import Permalink from 'flavours/glitch/components/permalink';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
|
||||||
const Account = connect(state => ({
|
const Account = connect(state => ({
|
||||||
account: state.getIn(['accounts', me]),
|
account: state.getIn(['accounts', me]),
|
||||||
|
@ -16,7 +17,14 @@ const Account = connect(state => ({
|
||||||
</Permalink>
|
</Permalink>
|
||||||
));
|
));
|
||||||
|
|
||||||
export default @withRouter
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
openClosedRegistrationsModal() {
|
||||||
|
dispatch(openModal('CLOSED_REGISTRATIONS'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(null, mapDispatchToProps)
|
||||||
|
@withRouter
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -24,12 +32,13 @@ class Header extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
openClosedRegistrationsModal: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { signedIn } = this.context.identity;
|
const { signedIn } = this.context.identity;
|
||||||
const { location } = this.props;
|
const { location, openClosedRegistrationsModal } = this.props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
|
@ -41,10 +50,26 @@ class Header extends React.PureComponent {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
let signupButton;
|
||||||
|
|
||||||
|
if (registrationsOpen) {
|
||||||
|
signupButton = (
|
||||||
|
<a href='/auth/sign_up' className='button button-tertiary'>
|
||||||
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
signupButton = (
|
||||||
|
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
|
||||||
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||||
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a>
|
{signupButton}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,180 @@
|
||||||
import inherited from 'mastodon/locales/cs.json';
|
import inherited from 'mastodon/locales/cs.json';
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
// No translations available.
|
'about.fork_disclaimer': 'Glitch-soc je svobodný software s otevřeným zdrojovým kódem založený na Mastodonu.',
|
||||||
|
'settings.layout_opts': 'Možnosti rozvržení',
|
||||||
|
'settings.layout': 'Rozložení:',
|
||||||
|
'layout.current_is': 'Nastavené rozložení je:',
|
||||||
|
'layout.auto': 'Automatické',
|
||||||
|
'layout.desktop': 'Desktop',
|
||||||
|
'layout.mobile': 'Mobil',
|
||||||
|
'layout.hint.auto': 'Vybrat rozložení automaticky v závislosti na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
|
||||||
|
'layout.hint.desktop': 'Použít vícesloupcové rozložení nezávisle na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
|
||||||
|
'layout.hint.single': 'Použít jednosloupcové rozložení nezávisle na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
|
||||||
|
'navigation_bar.app_settings': 'Nastavení aplikace',
|
||||||
|
'navigation_bar.featured_users': 'Vybraní uživatelé',
|
||||||
|
'endorsed_accounts_editor.endorsed_accounts': 'Vybrané účty',
|
||||||
|
'navigation_bar.info': 'Rozšířené informace',
|
||||||
|
'navigation_bar.misc': 'Různé',
|
||||||
|
'navigation_bar.keyboard_shortcuts': 'Klávesové zkratky',
|
||||||
|
'getting_started.onboarding': 'Ukaž mi to tu',
|
||||||
|
'onboarding.skip': 'Přeskočit',
|
||||||
|
'onboarding.next': 'Další',
|
||||||
|
'onboarding.done': 'Hotovo',
|
||||||
|
'onboarding.page_one.federation': '{domain} je \'instance\' Mastodonu. Mastodon je síť nezávislých serverů, které jsou spolu propojené do jedné velké sociální sítě. Těmto serverům říkáme instance.',
|
||||||
|
'onboarding.page_one.handle': 'Jste na instanci {domain}, takže celá adresa vašeho profilu je {handle}',
|
||||||
|
'onboarding.page_one.welcome': 'Vítá vás {domain}!',
|
||||||
|
'onboarding.page_two.compose': 'Příspěvky se píší v levém sloupci. Pomocí ikon pod příspěvkem k němu můžete připojit obrázky, změnit úroveň soukromí nebo přidat varování o obsahu.',
|
||||||
|
'onboarding.page_three.search': 'Pomocí vyhledávací lišty můžete hledat lidi nebo hashtagy. Pokud hledáte někoho z jiné instance, musíte použít celou adresu jeho profilu.',
|
||||||
|
'onboarding.page_three.profile': 'Upravte si svůj profil a nastavte si profilový obrázek, jméno, a krátký text o sobě. Naleznete tam i další možnosti nastavení.',
|
||||||
|
'onboarding.page_four.home': 'Domovská časová osa zobrazuje příspěvky od lidí, které sledujete.',
|
||||||
|
'onboarding.page_four.notifications': 'Notifikace se zobrazí, když s vámi někdo interaguje.',
|
||||||
|
'onboarding.page_five.public_timelines': 'Místní časová osa zobrazuje veřejné příspěvky všech uživatelů instance {domain}. Federovaná časová osa zobrazí příspěvky od všech, koho uživatelé instance {domain} sledují. Tyto veřejné časové osy jsou skvělý způsob, jak objevit nové lidi.',
|
||||||
|
'onboarding.page_six.almost_done': 'Skoro hotovo...',
|
||||||
|
'onboarding.page_six.github': 'Na serveru {domain} běží Glitchsoc. Glitchsoc je přátelský {fork} programu {Mastodon}, a je kompatibilní s jakoukoliv jinou mastodoní instancí nebo aplikací. Glitchsoc je zcela svobodný a má otevřený zdrojový kód. Na stránce {github} můžete hlásit chyby, žádat o nové funkce, nebo ke kódu vlastnoručně přispět.',
|
||||||
|
'onboarding.page_six.apps_available': 'Jsou dostupné {apps} pro iOS, Android i jiné platformy.',
|
||||||
|
'onboarding.page_six.various_app': 'mobilní aplikace',
|
||||||
|
'onboarding.page_six.appetoot': 'Veselé mastodonění!',
|
||||||
|
'settings.auto_collapse': 'Automaticky sbalit',
|
||||||
|
'settings.auto_collapse_all': 'Všechno',
|
||||||
|
'settings.auto_collapse_lengthy': 'Dlouhé příspěvky',
|
||||||
|
'settings.auto_collapse_media': 'Příspěvky s přílohami',
|
||||||
|
'settings.auto_collapse_notifications': 'Oznámení',
|
||||||
|
'settings.auto_collapse_reblogs': 'Boosty',
|
||||||
|
'settings.auto_collapse_replies': 'Odpovědi',
|
||||||
|
'settings.show_action_bar': 'Zobrazit ve sbalených příspěvcích tlačítka s akcemi',
|
||||||
|
'settings.close': 'Zavřít',
|
||||||
|
'settings.collapsed_statuses': 'Sbalené příspěvky',
|
||||||
|
'settings.confirm_boost_missing_media_description': 'Zobrazit potvrzovací dialog před boostnutím příspěvku s chybějícími popisky obrázků',
|
||||||
|
'boost_modal.missing_description': 'Příspěvek obsahuje obrázky bez popisků',
|
||||||
|
'settings.enable_collapsed': 'Povolit sbalené příspěvky',
|
||||||
|
'settings.enable_collapsed_hint': 'U sbalených příspěvků je část jejich obsahu skrytá, aby zabraly méně místa na obrazovce. (Tohle není stejná funkce jako varování o obsahu.)',
|
||||||
|
'settings.general': 'Obecné',
|
||||||
|
'settings.hicolor_privacy_icons': 'Barevné ikony soukromí',
|
||||||
|
'settings.hicolor_privacy_icons.hint': 'Zobrazit ikony úrovně soukromí příspěvků v jasných, snadno rozlišitelných barvách',
|
||||||
|
'settings.image_backgrounds': 'Obrázkové pozadí',
|
||||||
|
'settings.image_backgrounds_media': 'Náhled médií ve sbalených příspěvcích',
|
||||||
|
'settings.image_backgrounds_media_hint': 'Pokud jsou k příspěvku přiložena média, použije se první z nich jako pozadí',
|
||||||
|
'settings.image_backgrounds_users': 'Nastavit sbaleným příspěvkům obrázkové pozadí',
|
||||||
|
'settings.inline_preview_cards': 'Zobrazit v časové ose náhledy externích odkazů',
|
||||||
|
'settings.media': 'Média',
|
||||||
|
'settings.media_letterbox': 'Neořezávat obrázky',
|
||||||
|
'settings.media_letterbox_hint': 'Místo výřezu obrázku zobrazit obrázek celý, doplněný podle potřeby o prázdné okraje',
|
||||||
|
'settings.media_fullwidth': 'Zobrazit náhledy v plné šířce',
|
||||||
|
'settings.notifications_opts': 'Možnosti oznámení',
|
||||||
|
'settings.notifications.tab_badge': 'Zobrazit počet nepřečtených oznámení',
|
||||||
|
'settings.notifications.tab_badge.hint': 'Počet nepřečtených oznámení se viditelně zobrazí na hlavní stránce (pokud není seznam oznámení viditelný)',
|
||||||
|
'settings.notifications.favicon_badge': 'Zobrazit počet na ikoně serveru',
|
||||||
|
'settings.notifications.favicon_badge.hint': 'Zobrazí počet nepřečtených oznámení na ikoně serveru',
|
||||||
|
'settings.preferences': 'Předvolby',
|
||||||
|
'settings.rewrite_mentions': 'Přepsat zmínky v zobrazených příspěvcích',
|
||||||
|
'settings.rewrite_mentions_no': 'Nepřepisovat zmínky',
|
||||||
|
'settings.rewrite_mentions_acct': 'Přepsat uživatelským jménem a doménou (pokud je účet na jiném serveru)',
|
||||||
|
'settings.rewrite_mentions_username': 'Přepsat uživatelským jménem',
|
||||||
|
'settings.show_reply_counter': 'Zobrazit odhad počtu odpovědí',
|
||||||
|
'settings.status_icons': 'Ikony u příspěvků',
|
||||||
|
'settings.status_icons_language': 'Indikace jazyk',
|
||||||
|
'settings.status_icons_reply': 'Indikace odpovědi',
|
||||||
|
'settings.status_icons_local_only': 'Indikace lokálního příspěvku',
|
||||||
|
'settings.status_icons_media': 'Indikace obrázků a anket',
|
||||||
|
'settings.status_icons_visibility': 'Indikace úrovně soukromí',
|
||||||
|
'settings.tag_misleading_links': 'Označit zavádějící odkazy',
|
||||||
|
'settings.tag_misleading_links.hint': 'Zobrazit skutečný cíl u každého odkazu, který ho explicitně nezmiňuje',
|
||||||
|
'settings.wide_view': 'Široké sloupce (pouze v režimu Desktop)',
|
||||||
|
'settings.wide_view_hint': 'Sloupce se roztáhnout, aby lépe vyplnily dostupný prostor.',
|
||||||
|
'settings.navbar_under': 'Navigační lišta vespod (pouze v režimu Mobil)',
|
||||||
|
'settings.compose_box_opts': 'Editační pole',
|
||||||
|
'settings.always_show_spoilers_field': 'Vždy zobrazit pole pro varování o obsahu',
|
||||||
|
'settings.prepend_cw_re': 'Při odpovídání přidat před varování o obsahu “re: ”',
|
||||||
|
'settings.preselect_on_reply': 'Při odpovědi označit uživatelská jména',
|
||||||
|
'settings.preselect_on_reply_hint': 'Při odpovídání na konverzaci s více účastníky se jména všech kromě prvního označí, aby šla jednoduše smazat',
|
||||||
|
'settings.confirm_missing_media_description': 'Zobrazit potvrzovací dialog při odesílání příspěvku, ve kterém chybí popisky obrázků',
|
||||||
|
'settings.confirm_before_clearing_draft': 'Zobrazit potvrzovací dialog před přepsáním právě vytvářené zprávy',
|
||||||
|
'settings.show_content_type_choice': 'Zobrazit volbu formátu příspěvku',
|
||||||
|
'settings.side_arm': 'Vedlejší odesílací tlačítko:',
|
||||||
|
'settings.side_arm.none': 'Žádné',
|
||||||
|
'settings.side_arm_reply_mode': 'Při odpovídání na příspěvek by vedlejší odesílací tlačítko mělo:',
|
||||||
|
'settings.side_arm_reply_mode.keep': 'Použít svou nastavenou úroveň soukromí',
|
||||||
|
'settings.side_arm_reply_mode.copy': 'Použít úroveň soukromí příspěvku, na který odpovídáte',
|
||||||
|
'settings.side_arm_reply_mode.restrict': 'Zvýšit úroveň soukromí nejméně na úroveň příspěvku, na který odpovídáte',
|
||||||
|
'settings.content_warnings': 'Varování o obsahu',
|
||||||
|
'settings.content_warnings_shared_state': 'Zobrazit/schovat všechny kopie naráz',
|
||||||
|
'settings.content_warnings_shared_state_hint': 'Tlačítko varování o obsahu bude mít efekt na všechny kopie příspěvku naráz, stejně jako na běžném Mastodonu. Nebude pak možné automaticky sbalit jakoukoliv kopii příspěvku, která má rozbalené varování o obsahu',
|
||||||
|
'settings.content_warnings_media_outside': 'Zobrazit obrázky a videa mimo varování o obsahu',
|
||||||
|
'settings.content_warnings_media_outside_hint': 'Obrázky a videa z příspěvku s varováním o obsahu se zobrazí se separátním přepínačem zobrazení, stejně jako na běžném Mastodonu.',
|
||||||
|
'settings.content_warnings_unfold_opts': 'Možnosti automatického rozbalení',
|
||||||
|
'settings.enable_content_warnings_auto_unfold': 'Vždy rozbalit příspěvky označené varováním o obsahu',
|
||||||
|
'settings.deprecated_setting': 'Tato možnost se nyní nastavuje v {settings_page_link}',
|
||||||
|
'settings.shared_settings_link': 'předvolbách Mastodonu',
|
||||||
|
'settings.content_warnings_filter': 'Tato varování o obsahu automaticky nerozbalovat:',
|
||||||
|
'settings.content_warnings.regexp': 'Regulární výraz',
|
||||||
|
'settings.media_reveal_behind_cw': 'Automaticky zobrazit média označená varováním o obsahu',
|
||||||
|
'settings.pop_in_player': 'Povolit plovoucí okno přehrávače',
|
||||||
|
'settings.pop_in_position': 'Pozice plovoucího okna:',
|
||||||
|
'settings.pop_in_left': 'Vlevo',
|
||||||
|
'settings.pop_in_right': 'Vpravo',
|
||||||
|
|
||||||
|
|
||||||
|
'status.collapse': 'Sbalit',
|
||||||
|
'status.uncollapse': 'Rozbalit',
|
||||||
|
'status.in_reply_to': 'Tento příspěvek je odpověď',
|
||||||
|
'status.has_preview_card': 'Obsahuje náhled odkazu',
|
||||||
|
'status.has_pictures': 'Obsahuje obrázky',
|
||||||
|
'status.is_poll': 'Tento příspěvek je anketa',
|
||||||
|
'status.has_video': 'Obsahuje video',
|
||||||
|
'status.has_audio': 'Obsahuje audio',
|
||||||
|
'status.local_only': 'Viditelné pouze z vaší instance',
|
||||||
|
|
||||||
|
'media_gallery.sensitive': 'Citlivý obsah',
|
||||||
|
|
||||||
|
'favourite_modal.combo': 'Příště můžete pro přeskočení stisknout {combo}',
|
||||||
|
|
||||||
|
'home.column_settings.show_direct': 'Zobrazit přímé zprávy',
|
||||||
|
|
||||||
|
'notification_purge.start': 'Čistící režim',
|
||||||
|
'notifications.mark_as_read': 'Označit všechna oznámení jako přečtená',
|
||||||
|
|
||||||
|
'notification.markForDeletion': 'Označit pro smazání',
|
||||||
|
'notifications.clear': 'Vymazat všechna oznámení',
|
||||||
|
'notifications.marked_clear_confirmation': 'Určitě chcete trvale smazat všechna vybraná oznámení?',
|
||||||
|
'notifications.marked_clear': 'Smazat vybraná oznámení',
|
||||||
|
|
||||||
|
'notification_purge.btn_all': 'Vybrat\nvše',
|
||||||
|
'notification_purge.btn_none': 'Nevybrat\nnic',
|
||||||
|
'notification_purge.btn_invert': 'Obrátit\nvýběr',
|
||||||
|
'notification_purge.btn_apply': 'Smazat\nvybrané',
|
||||||
|
|
||||||
|
'compose.attach.upload': 'Nahrát soubor',
|
||||||
|
'compose.attach.doodle': 'Něco namalovat',
|
||||||
|
'compose.attach': 'Připojit...',
|
||||||
|
|
||||||
|
'advanced_options.local-only.short': 'Lokální příspěvek',
|
||||||
|
'advanced_options.local-only.long': 'Neposílat na jiné servery',
|
||||||
|
'advanced_options.local-only.tooltip': 'Tento příspěvek je pouze lokální',
|
||||||
|
'advanced_options.icon_title': 'Pokročilá nastavení',
|
||||||
|
'advanced_options.threaded_mode.short': 'Režim vlákna',
|
||||||
|
'advanced_options.threaded_mode.long': 'Po odeslání automaticky otevře pole pro odpověď',
|
||||||
|
'advanced_options.threaded_mode.tooltip': 'Režim vlákna je zapnutý',
|
||||||
|
|
||||||
|
'home.column_settings.advanced': 'Pokročilé',
|
||||||
|
'home.column_settings.filter_regex': 'Filtrovat podle regulárních výrazů',
|
||||||
|
|
||||||
|
'compose_form.poll.single_choice': 'Povolit jednu odpověď',
|
||||||
|
'compose_form.poll.multiple_choices': 'Povolit více odpovědí',
|
||||||
|
|
||||||
|
'compose.content-type.plain': 'Prostý text',
|
||||||
|
'content-type.change': 'Formát příspěvku',
|
||||||
|
'compose_form.spoiler': 'Přidat varování o obsahu',
|
||||||
|
|
||||||
|
'direct.group_by_conversations': 'Seskupit do konverzací',
|
||||||
|
'column.toot': 'Příspěvky a odpovědi',
|
||||||
|
'confirmation_modal.do_not_ask_again': 'Příště se už neptat',
|
||||||
|
|
||||||
|
'keyboard_shortcuts.bookmark': 'Přidat do záložek',
|
||||||
|
'keyboard_shortcuts.toggle_collapse': 'Sbalit/rozbalit příspěvek',
|
||||||
|
'keyboard_shortcuts.secondary_toot': 'Odeslat příspěvek s druhotným nastavením soukromí',
|
||||||
|
|
||||||
|
'column.subheading': 'Různé',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.assign({}, inherited, messages);
|
export default Object.assign({}, inherited, messages);
|
||||||
|
|
|
@ -6,6 +6,14 @@ en:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
default: Default
|
default: Default
|
||||||
|
cs:
|
||||||
|
flavours:
|
||||||
|
glitch:
|
||||||
|
description: Výchozí rozhraní instancí GlitchSoc.
|
||||||
|
name: Glitch
|
||||||
|
skins:
|
||||||
|
glitch:
|
||||||
|
default: Výchozí
|
||||||
pl:
|
pl:
|
||||||
flavours:
|
flavours:
|
||||||
glitch:
|
glitch:
|
||||||
|
|
|
@ -65,6 +65,7 @@ $ui-header-height: 55px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -81,10 +82,15 @@ $ui-header-height: 55px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-tertiary {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1306,7 +1306,8 @@ img.modal-warning {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
|
@ -6,6 +6,14 @@ en:
|
||||||
skins:
|
skins:
|
||||||
vanilla:
|
vanilla:
|
||||||
default: Default
|
default: Default
|
||||||
|
cs:
|
||||||
|
flavours:
|
||||||
|
vanilla:
|
||||||
|
description: Standardní rozhraní Mastodonu. Některé funkce GlitchSoc v něm nejsou podporované.
|
||||||
|
name: Standardní Mastodon
|
||||||
|
skins:
|
||||||
|
vanilla:
|
||||||
|
default: Výchozí
|
||||||
pl:
|
pl:
|
||||||
flavours:
|
flavours:
|
||||||
vanilla:
|
vanilla:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { registrationsOpen, me } from 'mastodon/initial_state';
|
||||||
import Avatar from 'mastodon/components/avatar';
|
import Avatar from 'mastodon/components/avatar';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
|
||||||
const Account = connect(state => ({
|
const Account = connect(state => ({
|
||||||
account: state.getIn(['accounts', me]),
|
account: state.getIn(['accounts', me]),
|
||||||
|
@ -15,7 +16,14 @@ const Account = connect(state => ({
|
||||||
</Link>
|
</Link>
|
||||||
));
|
));
|
||||||
|
|
||||||
export default @withRouter
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
openClosedRegistrationsModal() {
|
||||||
|
dispatch(openModal('CLOSED_REGISTRATIONS'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(null, mapDispatchToProps)
|
||||||
|
@withRouter
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -23,12 +31,13 @@ class Header extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
openClosedRegistrationsModal: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { signedIn } = this.context.identity;
|
const { signedIn } = this.context.identity;
|
||||||
const { location } = this.props;
|
const { location, openClosedRegistrationsModal } = this.props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
|
@ -40,10 +49,26 @@ class Header extends React.PureComponent {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
let signupButton;
|
||||||
|
|
||||||
|
if (registrationsOpen) {
|
||||||
|
signupButton = (
|
||||||
|
<a href='/auth/sign_up' className='button button-tertiary'>
|
||||||
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
signupButton = (
|
||||||
|
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
|
||||||
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||||
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a>
|
{signupButton}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,10 @@ en:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
contrast: High contrast
|
contrast: High contrast
|
||||||
|
cs:
|
||||||
|
skins:
|
||||||
|
glitch:
|
||||||
|
contrast: Vysoký kontrast
|
||||||
es:
|
es:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
|
|
|
@ -2,6 +2,10 @@ en:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
mastodon-light: Mastodon (light)
|
mastodon-light: Mastodon (light)
|
||||||
|
cs:
|
||||||
|
skins:
|
||||||
|
glitch:
|
||||||
|
mastodon-light: Mastodon (světlý)
|
||||||
es:
|
es:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
|
|
|
@ -2,6 +2,10 @@ en:
|
||||||
skins:
|
skins:
|
||||||
vanilla:
|
vanilla:
|
||||||
contrast: High contrast
|
contrast: High contrast
|
||||||
|
cs:
|
||||||
|
skins:
|
||||||
|
vanilla:
|
||||||
|
contrast: Vysoký kontrast
|
||||||
es:
|
es:
|
||||||
skins:
|
skins:
|
||||||
vanilla:
|
vanilla:
|
||||||
|
|
|
@ -2,6 +2,10 @@ en:
|
||||||
skins:
|
skins:
|
||||||
vanilla:
|
vanilla:
|
||||||
mastodon-light: Mastodon (light)
|
mastodon-light: Mastodon (light)
|
||||||
|
cs:
|
||||||
|
skins:
|
||||||
|
vanilla:
|
||||||
|
mastodon-light: Mastodon (světlý)
|
||||||
es:
|
es:
|
||||||
skins:
|
skins:
|
||||||
glitch:
|
glitch:
|
||||||
|
|
|
@ -2,3 +2,7 @@ en:
|
||||||
skins:
|
skins:
|
||||||
vanilla:
|
vanilla:
|
||||||
win95: Masto95
|
win95: Masto95
|
||||||
|
cs:
|
||||||
|
skins:
|
||||||
|
vanilla:
|
||||||
|
win95: Windows 95
|
||||||
|
|
|
@ -2231,6 +2231,7 @@ $ui-header-height: 55px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -2247,10 +2248,15 @@ $ui-header-height: 55px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-tertiary {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7156,10 +7162,12 @@ noscript {
|
||||||
|
|
||||||
.verified {
|
.verified {
|
||||||
border: 1px solid rgba($valid-value-color, 0.5);
|
border: 1px solid rgba($valid-value-color, 0.5);
|
||||||
|
margin-top: -1px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -7991,7 +7999,8 @@ noscript {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
|
@ -10,7 +10,7 @@ class DeliveryFailureTracker
|
||||||
end
|
end
|
||||||
|
|
||||||
def track_failure!
|
def track_failure!
|
||||||
redis.sadd(exhausted_deliveries_key, today)
|
redis.sadd?(exhausted_deliveries_key, today)
|
||||||
UnavailableDomain.create(domain: @host) if reached_failure_threshold?
|
UnavailableDomain.create(domain: @host) if reached_failure_threshold?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -322,24 +322,24 @@ class FeedManager
|
||||||
def clean_feeds!(type, ids)
|
def clean_feeds!(type, ids)
|
||||||
reblogged_id_sets = {}
|
reblogged_id_sets = {}
|
||||||
|
|
||||||
redis.pipelined do
|
redis.pipelined do |pipeline|
|
||||||
ids.each do |feed_id|
|
ids.each do |feed_id|
|
||||||
redis.del(key(type, feed_id))
|
pipeline.del(key(type, feed_id))
|
||||||
reblog_key = key(type, feed_id, 'reblogs')
|
reblog_key = key(type, feed_id, 'reblogs')
|
||||||
# We collect a future for this: we don't block while getting
|
# We collect a future for this: we don't block while getting
|
||||||
# it, but we can iterate over it later.
|
# it, but we can iterate over it later.
|
||||||
reblogged_id_sets[feed_id] = redis.zrange(reblog_key, 0, -1)
|
reblogged_id_sets[feed_id] = pipeline.zrange(reblog_key, 0, -1)
|
||||||
redis.del(reblog_key)
|
pipeline.del(reblog_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove all of the reblog tracking keys we just removed the
|
# Remove all of the reblog tracking keys we just removed the
|
||||||
# references to.
|
# references to.
|
||||||
redis.pipelined do
|
redis.pipelined do |pipeline|
|
||||||
reblogged_id_sets.each do |feed_id, future|
|
reblogged_id_sets.each do |feed_id, future|
|
||||||
future.value.each do |reblogged_id|
|
future.value.each do |reblogged_id|
|
||||||
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}")
|
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}")
|
||||||
redis.del(reblog_set_key)
|
pipeline.del(reblog_set_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -519,7 +519,7 @@ class FeedManager
|
||||||
# REBLOG_FALLOFF most recent statuses, so we note that this
|
# REBLOG_FALLOFF most recent statuses, so we note that this
|
||||||
# is an "extra" reblog, by storing it in reblog_set_key.
|
# is an "extra" reblog, by storing it in reblog_set_key.
|
||||||
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
|
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
|
||||||
redis.sadd(reblog_set_key, status.id)
|
redis.sadd?(reblog_set_key, status.id)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -556,7 +556,7 @@ class FeedManager
|
||||||
# 2. Remove reblog from set of this status's reblogs.
|
# 2. Remove reblog from set of this status's reblogs.
|
||||||
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
|
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
|
||||||
|
|
||||||
redis.srem(reblog_set_key, status.id)
|
redis.srem?(reblog_set_key, status.id)
|
||||||
redis.zrem(reblog_key, status.reblog_of_id)
|
redis.zrem(reblog_key, status.reblog_of_id)
|
||||||
# 3. Re-insert another reblog or original into the feed if one
|
# 3. Re-insert another reblog or original into the feed if one
|
||||||
# remains in the set. We could pick a random element, but this
|
# remains in the set. We could pick a random element, but this
|
||||||
|
|
|
@ -42,6 +42,6 @@ class Vacuum::StatusesVacuum
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_search_index(status_ids)
|
def remove_from_search_index(status_ids)
|
||||||
with_redis { |redis| redis.sadd('chewy:queue:StatusesIndex', status_ids) }
|
with_redis { |redis| redis.sadd?('chewy:queue:StatusesIndex', status_ids) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,7 +59,7 @@ class AccountMigration < ApplicationRecord
|
||||||
|
|
||||||
def set_target_account
|
def set_target_account
|
||||||
self.target_account = ResolveAccountService.new.call(acct, skip_cache: true)
|
self.target_account = ResolveAccountService.new.call(acct, skip_cache: true)
|
||||||
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
|
||||||
# Validation will take care of it
|
# Validation will take care of it
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ module DomainMaterializable
|
||||||
|
|
||||||
Instance.refresh
|
Instance.refresh
|
||||||
count_unique_subdomains!
|
count_unique_subdomains!
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def count_unique_subdomains!
|
def count_unique_subdomains!
|
||||||
|
|
|
@ -54,7 +54,7 @@ class CustomFilter < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def irreversible=(value)
|
def irreversible=(value)
|
||||||
self.action = value ? :hide : :warn
|
self.action = ActiveModel::Type::Boolean.new.cast(value) ? :hide : :warn
|
||||||
end
|
end
|
||||||
|
|
||||||
def irreversible?
|
def irreversible?
|
||||||
|
|
|
@ -19,9 +19,9 @@ class FollowRecommendationSuppression < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def remove_follow_recommendations
|
def remove_follow_recommendations
|
||||||
redis.pipelined do
|
redis.pipelined do |pipeline|
|
||||||
I18n.available_locales.each do |locale|
|
I18n.available_locales.each do |locale|
|
||||||
redis.zrem("follow_recommendations:#{locale}", account_id)
|
pipeline.zrem("follow_recommendations:#{locale}", account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Form::Redirect
|
||||||
|
|
||||||
def set_target_account
|
def set_target_account
|
||||||
@target_account = ResolveAccountService.new.call(acct, skip_cache: true)
|
@target_account = ResolveAccountService.new.call(acct, skip_cache: true)
|
||||||
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
|
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
|
||||||
# Validation will take care of it
|
# Validation will take care of it
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ class Trends::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_used_id(id, at_time = Time.now.utc)
|
def record_used_id(id, at_time = Time.now.utc)
|
||||||
redis.sadd(used_key(at_time), id)
|
redis.sadd?(used_key(at_time), id)
|
||||||
redis.expire(used_key(at_time), 1.day.seconds)
|
redis.expire(used_key(at_time), 1.day.seconds)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,9 @@ class BatchedRemoveStatusService < BaseService
|
||||||
|
|
||||||
# Cannot be batched
|
# Cannot be batched
|
||||||
@status_id_cutoff = Mastodon::Snowflake.id_at(2.weeks.ago)
|
@status_id_cutoff = Mastodon::Snowflake.id_at(2.weeks.ago)
|
||||||
redis.pipelined do
|
redis.pipelined do |pipeline|
|
||||||
statuses.each do |status|
|
statuses.each do |status|
|
||||||
unpush_from_public_timelines(status)
|
unpush_from_public_timelines(pipeline, status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -73,22 +73,22 @@ class BatchedRemoveStatusService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unpush_from_public_timelines(status)
|
def unpush_from_public_timelines(pipeline, status)
|
||||||
return unless status.public_visibility? && status.id > @status_id_cutoff
|
return unless status.public_visibility? && status.id > @status_id_cutoff
|
||||||
|
|
||||||
payload = Oj.dump(event: :delete, payload: status.id.to_s)
|
payload = Oj.dump(event: :delete, payload: status.id.to_s)
|
||||||
|
|
||||||
redis.publish('timeline:public', payload)
|
pipeline.publish('timeline:public', payload)
|
||||||
redis.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
|
pipeline.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
|
||||||
|
|
||||||
if status.media_attachments.any?
|
if status.media_attachments.any?
|
||||||
redis.publish('timeline:public:media', payload)
|
pipeline.publish('timeline:public:media', payload)
|
||||||
redis.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
|
pipeline.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag|
|
status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag|
|
||||||
redis.publish("timeline:hashtag:#{hashtag}", payload)
|
pipeline.publish("timeline:hashtag:#{hashtag}", payload)
|
||||||
redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
|
pipeline.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Scheduler::IndexingScheduler
|
||||||
type.import!(ids)
|
type.import!(ids)
|
||||||
|
|
||||||
redis.pipelined do |pipeline|
|
redis.pipelined do |pipeline|
|
||||||
ids.each { |id| pipeline.srem("chewy:queue:#{type.name}", id) }
|
ids.each { |id| pipeline.srem?("chewy:queue:#{type.name}", id) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,6 +71,7 @@ module Mastodon
|
||||||
:af,
|
:af,
|
||||||
:ar,
|
:ar,
|
||||||
:ast,
|
:ast,
|
||||||
|
:be,
|
||||||
:bg,
|
:bg,
|
||||||
:bn,
|
:bn,
|
||||||
:br,
|
:br,
|
||||||
|
|
|
@ -2,6 +2,7 @@ default: &default
|
||||||
adapter: postgresql
|
adapter: postgresql
|
||||||
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
|
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
|
connect_timeout: 15
|
||||||
encoding: unicode
|
encoding: unicode
|
||||||
sslmode: <%= ENV['DB_SSLMODE'] || "prefer" %>
|
sslmode: <%= ENV['DB_SSLMODE'] || "prefer" %>
|
||||||
|
|
||||||
|
|
41
config/locales-glitch/cs.yml
Normal file
41
config/locales-glitch/cs.yml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
cs:
|
||||||
|
admin:
|
||||||
|
custom_emojis:
|
||||||
|
batch_copy_error: 'Při kopírování některých emoji došlo k chybě: %{message}'
|
||||||
|
batch_error: 'Došlo k chybě: %{message}'
|
||||||
|
settings:
|
||||||
|
captcha_enabled:
|
||||||
|
desc_html: Tato funkce používá externí skripty služby hCaptcha, což může být problém z hlediska bezpečí a ochrany dat. Také to může <strong>některým (hlavně postiženým) lidem registrační proces výrazně zkomplikovat</strong>. Z tohoto důvodu prosím raději zvažte jiné možnosti, jako je schvalování registrací nebo registrace pouze pro zvané.<br>Uživatelům pozvaným skrze omezenou pozvánku se CAPTCHA nezobrazí.
|
||||||
|
title: Vyžadovat po nových uživatelích opsání textu z obrázku (CAPTCHA)
|
||||||
|
enable_keybase:
|
||||||
|
desc_html: Uživatelé budou moci potvrdit svou identitu pomocí Keybase
|
||||||
|
title: Zapnout potvrzování pomocí Keybase
|
||||||
|
flavour_and_skin:
|
||||||
|
title: Rozhraní a styl
|
||||||
|
other:
|
||||||
|
preamble: Různá nastavení glitch-soc, která se nevešla do jiných kategorií.
|
||||||
|
title: Jiné
|
||||||
|
outgoing_spoilers:
|
||||||
|
desc_html: Při federování příspěvků se přidá toto varování o obsahu příspěvkům, které žádné nemají. To může být užitečné, pokud je váš server zaměřen na specifický obsah, pro který by jiné servery mohly varování o obsahu vyžadovat. Připojená média budou označena jako citlivá.
|
||||||
|
title: Varování o obsahu pro odesílané příspěvky
|
||||||
|
hide_followers_count:
|
||||||
|
desc_html: Nezobrazovat na uživatelských profilech počet sledujících
|
||||||
|
title: Schovat počet sledujících
|
||||||
|
show_reblogs_in_public_timelines:
|
||||||
|
desc_html: Veřejné boosty veřejných příspěvků se zobrazí na místní a federované časové ose.
|
||||||
|
title: Zobrazovat ve veřejných časových osách boosty
|
||||||
|
show_replies_in_public_timelines:
|
||||||
|
desc_html: Na místní a federované časové ose se kromě odpovědí autora na vlastní příspěvky (vláken) zobrazí i ostatní veřejné odpovědi.
|
||||||
|
title: Zobrazovat ve veřejných časových osách odpovědi
|
||||||
|
trending_status_cw:
|
||||||
|
desc_html: Zobrazovat v rámci trendů (pokud jsou zapnuté) i příspěvky s varováním o obsahu. Změny tohoto nastavení se neprojeví retroaktivně.
|
||||||
|
title: Povolit v trendech příspěvky s varováním o obsahu
|
||||||
|
auth:
|
||||||
|
captcha_confirmation:
|
||||||
|
hint_html: Už jen poslední krok! Pro potvrzení svého účtu opište prosím text z obrázku (CAPTCHA). Pokud máte dotazy nebo potřebujete s potvrzením pomoct, můžete <a href="/about/more">kontaktovat administrátora</a>.
|
||||||
|
title: Ověření uživatele
|
||||||
|
generic:
|
||||||
|
use_this: Použít
|
||||||
|
settings:
|
||||||
|
flavours: Rozhraní
|
27
config/locales-glitch/simple_form.cs.yml
Normal file
27
config/locales-glitch/simple_form.cs.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
cs:
|
||||||
|
simple_form:
|
||||||
|
glitch_only: glitch-soc
|
||||||
|
hints:
|
||||||
|
defaults:
|
||||||
|
fields: Na svém profilu můžete mít zobrazeno několik položek (max. %{count}) jako tabulku
|
||||||
|
setting_default_content_type_html: Předpokládat, že nové příspěvky jsou napsané v HTML, pokud není uvedeno jinak
|
||||||
|
setting_default_content_type_markdown: Předpokládat, že nové příspěvky používají pro formátování Markdown, pokud není uvedeno jinak
|
||||||
|
setting_default_content_type_plain: Předpokládat, že nové příspěvky nejsou nijak formátované, pokud není uvedeno jinak (standardní chování Mastodonu)
|
||||||
|
setting_default_language: Jazyk vašich příspěvků lze detekovat automaticky, ale není to vždycky přesné
|
||||||
|
setting_hide_followers_count: Počet vašich sledujících se nebude nikomu zobrazovat, ani vám. Některé aplikace mohou zobrazit negativní počet sledujících.
|
||||||
|
setting_skin: Aplikuje barevný styl na zvolené rozhraní Mastodonu
|
||||||
|
labels:
|
||||||
|
defaults:
|
||||||
|
setting_default_content_type: Výchozí formát příspěvků
|
||||||
|
setting_default_content_type_html: HTML
|
||||||
|
setting_default_content_type_markdown: Markdown
|
||||||
|
setting_default_content_type_plain: Prostý text
|
||||||
|
setting_favourite_modal: Před oblíbením příspěvku zobrazit potvrzovací dialog (pouze pro rozhraní Glitch)
|
||||||
|
setting_hide_followers_count: Skrýt počet vašich sledujících
|
||||||
|
setting_skin: Styl
|
||||||
|
setting_system_emoji_font: Použít výchozí emoji systému (pouze pro rozhraní Glitch)
|
||||||
|
notification_emails:
|
||||||
|
trending_tag: Nový populární hashtag vyžaduje schválení
|
||||||
|
trending_link: Nový populární odkaz vyžaduje schválení
|
||||||
|
trending_status: Nový populární příspěvek vyžaduje schválení
|
|
@ -79,69 +79,72 @@ class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
|
||||||
safety_assured do
|
safety_assured do
|
||||||
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
|
||||||
next if log.account.nil?
|
next if log.account.nil?
|
||||||
log.update(human_identifier: log.account.acct)
|
log.update_attribute('human_identifier', log.account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
|
||||||
next if log.user.nil?
|
next if log.user.nil?
|
||||||
log.update(human_identifier: log.user.account.acct, route_param: log.user.account_id)
|
log.update_attribute('human_identifier', log.user.account.acct)
|
||||||
|
log.update_attribute('route_param', log.user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
|
Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
|
||||||
|
|
||||||
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
|
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
|
||||||
next if log.domain_block.nil?
|
next if log.domain_block.nil?
|
||||||
log.update(human_identifier: log.domain_block.domain)
|
log.update_attribute('human_identifier', log.domain_block.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
|
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
|
||||||
next if log.domain_allow.nil?
|
next if log.domain_allow.nil?
|
||||||
log.update(human_identifier: log.domain_allow.domain)
|
log.update_attribute('human_identifier', log.domain_allow.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
|
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
|
||||||
next if log.email_domain_block.nil?
|
next if log.email_domain_block.nil?
|
||||||
log.update(human_identifier: log.email_domain_block.domain)
|
log.update_attribute('human_identifier', log.email_domain_block.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
|
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
|
||||||
next if log.unavailable_domain.nil?
|
next if log.unavailable_domain.nil?
|
||||||
log.update(human_identifier: log.unavailable_domain.domain)
|
log.update_attribute('human_identifier', log.unavailable_domain.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
|
||||||
next if log.status.nil?
|
next if log.status.nil?
|
||||||
log.update(human_identifier: log.status.account.acct, permalink: log.status.uri)
|
log.update_attribute('human_identifier', log.status.account.acct)
|
||||||
|
log.update_attribute('permalink', log.status.uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
|
||||||
next if log.account_warning.nil?
|
next if log.account_warning.nil?
|
||||||
log.update(human_identifier: log.account_warning.account.acct)
|
log.update_attribute('human_identifier', log.account_warning.account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
|
||||||
next if log.announcement.nil?
|
next if log.announcement.nil?
|
||||||
log.update(human_identifier: log.announcement.text)
|
log.update_attribute('human_identifier', log.announcement.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
|
||||||
next if log.ip_block.nil?
|
next if log.ip_block.nil?
|
||||||
log.update(human_identifier: "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
|
log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
|
||||||
next if log.custom_emoji.nil?
|
next if log.custom_emoji.nil?
|
||||||
log.update(human_identifier: log.custom_emoji.shortcode)
|
log.update_attribute('human_identifier', log.custom_emoji.shortcode)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
|
||||||
next if log.canonical_email_block.nil?
|
next if log.canonical_email_block.nil?
|
||||||
log.update(human_identifier: log.canonical_email_block.canonical_email_hash)
|
log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
|
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
|
||||||
next if log.appeal.nil?
|
next if log.appeal.nil?
|
||||||
log.update(human_identifier: log.appeal.account.acct, route_param: log.appeal.account_warning_id)
|
log.update_attribute('human_identifier', log.appeal.account.acct)
|
||||||
|
log.update_attribute('route_param', log.appeal.account_warning_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class BackfillAdminActionLogsAgain < ActiveRecord::Migration[6.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
class Account < ApplicationRecord
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
has_one :user, inverse_of: :account
|
||||||
|
|
||||||
|
def local?
|
||||||
|
domain.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def acct
|
||||||
|
local? ? username : "#{username}@#{domain}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class User < ApplicationRecord
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
belongs_to :account
|
||||||
|
end
|
||||||
|
|
||||||
|
class Status < ApplicationRecord
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
def local?
|
||||||
|
attributes['local'] || attributes['uri'].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def uri
|
||||||
|
local? ? activity_account_status_url(account, self) : attributes['uri']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DomainBlock < ApplicationRecord; end
|
||||||
|
class DomainAllow < ApplicationRecord; end
|
||||||
|
class EmailDomainBlock < ApplicationRecord; end
|
||||||
|
class UnavailableDomain < ApplicationRecord; end
|
||||||
|
|
||||||
|
class AccountWarning < ApplicationRecord
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
belongs_to :account
|
||||||
|
end
|
||||||
|
|
||||||
|
class Announcement < ApplicationRecord; end
|
||||||
|
class IpBlock < ApplicationRecord; end
|
||||||
|
class CustomEmoji < ApplicationRecord; end
|
||||||
|
class CanonicalEmailBlock < ApplicationRecord; end
|
||||||
|
|
||||||
|
class Appeal < ApplicationRecord
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
belongs_to :account
|
||||||
|
end
|
||||||
|
|
||||||
|
class AdminActionLog < ApplicationRecord
|
||||||
|
# Dummy class, to make migration possible across version changes
|
||||||
|
|
||||||
|
# Cannot use usual polymorphic support because of namespacing issues
|
||||||
|
belongs_to :status, foreign_key: :target_id
|
||||||
|
belongs_to :account, foreign_key: :target_id
|
||||||
|
belongs_to :user, foreign_key: :user_id
|
||||||
|
belongs_to :domain_block, foreign_key: :target_id
|
||||||
|
belongs_to :domain_allow, foreign_key: :target_id
|
||||||
|
belongs_to :email_domain_block, foreign_key: :target_id
|
||||||
|
belongs_to :unavailable_domain, foreign_key: :target_id
|
||||||
|
belongs_to :account_warning, foreign_key: :target_id
|
||||||
|
belongs_to :announcement, foreign_key: :target_id
|
||||||
|
belongs_to :ip_block, foreign_key: :target_id
|
||||||
|
belongs_to :custom_emoji, foreign_key: :target_id
|
||||||
|
belongs_to :canonical_email_block, foreign_key: :target_id
|
||||||
|
belongs_to :appeal, foreign_key: :target_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
safety_assured do
|
||||||
|
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.account.nil?
|
||||||
|
log.update_attribute('human_identifier', log.account.acct)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.user.nil?
|
||||||
|
log.update_attribute('human_identifier', log.user.account.acct)
|
||||||
|
log.update_attribute('route_param', log.user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
|
||||||
|
|
||||||
|
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
|
||||||
|
next if log.domain_block.nil?
|
||||||
|
log.update_attribute('human_identifier', log.domain_block.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
|
||||||
|
next if log.domain_allow.nil?
|
||||||
|
log.update_attribute('human_identifier', log.domain_allow.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
|
||||||
|
next if log.email_domain_block.nil?
|
||||||
|
log.update_attribute('human_identifier', log.email_domain_block.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
|
||||||
|
next if log.unavailable_domain.nil?
|
||||||
|
log.update_attribute('human_identifier', log.unavailable_domain.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.status.nil?
|
||||||
|
log.update_attribute('human_identifier', log.status.account.acct)
|
||||||
|
log.update_attribute('permalink', log.status.uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.account_warning.nil?
|
||||||
|
log.update_attribute('human_identifier', log.account_warning.account.acct)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.announcement.nil?
|
||||||
|
log.update_attribute('human_identifier', log.announcement.text)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.ip_block.nil?
|
||||||
|
log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.custom_emoji.nil?
|
||||||
|
log.update_attribute('human_identifier', log.custom_emoji.shortcode)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.canonical_email_block.nil?
|
||||||
|
log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
|
||||||
|
next if log.appeal.nil?
|
||||||
|
log.update_attribute('human_identifier', log.appeal.account.acct)
|
||||||
|
log.update_attribute('route_param', log.appeal.account_warning_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -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: 2022_11_24_114030) do
|
ActiveRecord::Schema.define(version: 2022_12_06_114142) 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"
|
||||||
|
@ -781,16 +781,6 @@ ActiveRecord::Schema.define(version: 2022_11_24_114030) do
|
||||||
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
|
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "reactions", force: :cascade do |t|
|
|
||||||
t.string "emoji"
|
|
||||||
t.bigint "status_id", null: false
|
|
||||||
t.bigint "account_id", null: false
|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
|
||||||
t.index ["account_id"], name: "index_reactions_on_account_id"
|
|
||||||
t.index ["status_id"], name: "index_reactions_on_status_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "relays", force: :cascade do |t|
|
create_table "relays", force: :cascade do |t|
|
||||||
t.string "inbox_url", default: "", null: false
|
t.string "inbox_url", default: "", null: false
|
||||||
t.string "follow_activity_id"
|
t.string "follow_activity_id"
|
||||||
|
@ -1221,8 +1211,6 @@ ActiveRecord::Schema.define(version: 2022_11_24_114030) do
|
||||||
add_foreign_key "polls", "accounts", on_delete: :cascade
|
add_foreign_key "polls", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "polls", "statuses", on_delete: :cascade
|
add_foreign_key "polls", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade
|
add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade
|
||||||
add_foreign_key "reactions", "accounts"
|
|
||||||
add_foreign_key "reactions", "statuses"
|
|
||||||
add_foreign_key "report_notes", "accounts", on_delete: :cascade
|
add_foreign_key "report_notes", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "report_notes", "reports", on_delete: :cascade
|
add_foreign_key "report_notes", "reports", on_delete: :cascade
|
||||||
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify
|
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify
|
||||||
|
|
|
@ -17,7 +17,7 @@ module Chewy
|
||||||
RedisConfiguration.with do |redis|
|
RedisConfiguration.with do |redis|
|
||||||
redis.pipelined do |pipeline|
|
redis.pipelined do |pipeline|
|
||||||
@stash.each do |type, ids|
|
@stash.each do |type, ids|
|
||||||
pipeline.sadd("chewy:queue:#{type.name}", ids)
|
pipeline.sadd?("chewy:queue:#{type.name}", ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,8 +54,8 @@ module Mastodon
|
||||||
def clear
|
def clear
|
||||||
keys = redis.keys('feed:*')
|
keys = redis.keys('feed:*')
|
||||||
|
|
||||||
redis.pipelined do
|
redis.pipelined do |pipeline|
|
||||||
keys.each { |key| redis.del(key) }
|
keys.each { |key| pipeline.del(key) }
|
||||||
end
|
end
|
||||||
|
|
||||||
say('OK', :green)
|
say('OK', :green)
|
||||||
|
|
|
@ -25,7 +25,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def suffix_version
|
def suffix_version
|
||||||
'+1.0.10'
|
'+1.0.11'
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_suffix
|
def post_suffix
|
||||||
|
|
|
@ -43,6 +43,16 @@ namespace :tests do
|
||||||
puts 'CustomFilterKeyword records not created as expected'
|
puts 'CustomFilterKeyword records not created as expected'
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless Admin::ActionLog.find_by(target_type: 'DomainBlock', target_id: 1).human_identifier == 'example.org'
|
||||||
|
puts 'Admin::ActionLog domain block records not updated as expected'
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless Admin::ActionLog.find_by(target_type: 'EmailDomainBlock', target_id: 1).human_identifier == 'example.org'
|
||||||
|
puts 'Admin::ActionLog email domain block records not updated as expected'
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Populate the database with test data for 2.4.3'
|
desc 'Populate the database with test data for 2.4.3'
|
||||||
|
@ -84,8 +94,8 @@ namespace :tests do
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'destroy', 'Account', 1, now(), now()),
|
(1, 'destroy', 'Account', 1, now(), now()),
|
||||||
(1, 'destroy', 'User', 1, now(), now()),
|
(1, 'destroy', 'User', 1, now(), now()),
|
||||||
(1, 'destroy', 'DomainBlock', 1312, now(), now()),
|
(1, 'destroy', 'DomainBlock', 1, now(), now()),
|
||||||
(1, 'destroy', 'EmailDomainBlock', 1312, now(), now()),
|
(1, 'destroy', 'EmailDomainBlock', 1, now(), now()),
|
||||||
(1, 'destroy', 'Status', 1, now(), now()),
|
(1, 'destroy', 'Status', 1, now(), now()),
|
||||||
(1, 'destroy', 'CustomEmoji', 3, now(), now());
|
(1, 'destroy', 'CustomEmoji', 3, now(), now());
|
||||||
SQL
|
SQL
|
||||||
|
|
|
@ -147,6 +147,87 @@ RSpec.describe Admin::AccountsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST #approve' do
|
||||||
|
subject { post :approve, params: { id: account.id } }
|
||||||
|
|
||||||
|
let(:current_user) { Fabricate(:user, role: role) }
|
||||||
|
let(:account) { user.account }
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
account.user.update(approved: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is admin' do
|
||||||
|
let(:role) { UserRole.find_by(name: 'Admin') }
|
||||||
|
|
||||||
|
it 'succeeds in approving account' do
|
||||||
|
is_expected.to redirect_to admin_accounts_path(status: 'pending')
|
||||||
|
expect(user.reload).to be_approved
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs action' do
|
||||||
|
is_expected.to have_http_status :found
|
||||||
|
|
||||||
|
log_item = Admin::ActionLog.last
|
||||||
|
|
||||||
|
expect(log_item).to_not be_nil
|
||||||
|
expect(log_item.action).to eq :approve
|
||||||
|
expect(log_item.account_id).to eq current_user.account_id
|
||||||
|
expect(log_item.target_id).to eq account.user.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not admin' do
|
||||||
|
let(:role) { UserRole.everyone }
|
||||||
|
|
||||||
|
it 'fails to approve account' do
|
||||||
|
is_expected.to have_http_status :forbidden
|
||||||
|
expect(user.reload).not_to be_approved
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST #reject' do
|
||||||
|
subject { post :reject, params: { id: account.id } }
|
||||||
|
|
||||||
|
let(:current_user) { Fabricate(:user, role: role) }
|
||||||
|
let(:account) { user.account }
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
account.user.update(approved: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is admin' do
|
||||||
|
let(:role) { UserRole.find_by(name: 'Admin') }
|
||||||
|
|
||||||
|
it 'succeeds in rejecting account' do
|
||||||
|
is_expected.to redirect_to admin_accounts_path(status: 'pending')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs action' do
|
||||||
|
is_expected.to have_http_status :found
|
||||||
|
|
||||||
|
log_item = Admin::ActionLog.last
|
||||||
|
|
||||||
|
expect(log_item).to_not be_nil
|
||||||
|
expect(log_item.action).to eq :reject
|
||||||
|
expect(log_item.account_id).to eq current_user.account_id
|
||||||
|
expect(log_item.target_id).to eq account.user.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not admin' do
|
||||||
|
let(:role) { UserRole.everyone }
|
||||||
|
|
||||||
|
it 'fails to reject account' do
|
||||||
|
is_expected.to have_http_status :forbidden
|
||||||
|
expect(user.reload).not_to be_approved
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST #redownload' do
|
describe 'POST #redownload' do
|
||||||
subject { post :redownload, params: { id: account.id } }
|
subject { post :redownload, params: { id: account.id } }
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
|
||||||
it 'approves user' do
|
it 'approves user' do
|
||||||
expect(account.reload.user_approved?).to be true
|
expect(account.reload.user_approved?).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'logs action' do
|
||||||
|
log_item = Admin::ActionLog.last
|
||||||
|
|
||||||
|
expect(log_item).to_not be_nil
|
||||||
|
expect(log_item.action).to eq :approve
|
||||||
|
expect(log_item.account_id).to eq user.account_id
|
||||||
|
expect(log_item.target_id).to eq account.user.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #reject' do
|
describe 'POST #reject' do
|
||||||
|
@ -118,6 +127,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
|
||||||
it 'removes user' do
|
it 'removes user' do
|
||||||
expect(User.where(id: account.user.id).count).to eq 0
|
expect(User.where(id: account.user.id).count).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'logs action' do
|
||||||
|
log_item = Admin::ActionLog.last
|
||||||
|
|
||||||
|
expect(log_item).to_not be_nil
|
||||||
|
expect(log_item.action).to eq :reject
|
||||||
|
expect(log_item.account_id).to eq user.account_id
|
||||||
|
expect(log_item.target_id).to eq account.user.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #enable' do
|
describe 'POST #enable' do
|
||||||
|
|
|
@ -22,9 +22,11 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
|
||||||
|
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
let(:scopes) { 'write:filters' }
|
let(:scopes) { 'write:filters' }
|
||||||
|
let(:irreversible) { true }
|
||||||
|
let(:whole_word) { false }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
post :create, params: { phrase: 'magic', context: %w(home), irreversible: true }
|
post :create, params: { phrase: 'magic', context: %w(home), irreversible: irreversible, whole_word: whole_word }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
|
@ -34,11 +36,29 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
|
||||||
it 'creates a filter' do
|
it 'creates a filter' do
|
||||||
filter = user.account.custom_filters.first
|
filter = user.account.custom_filters.first
|
||||||
expect(filter).to_not be_nil
|
expect(filter).to_not be_nil
|
||||||
expect(filter.keywords.pluck(:keyword)).to eq ['magic']
|
expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]]
|
||||||
expect(filter.context).to eq %w(home)
|
expect(filter.context).to eq %w(home)
|
||||||
expect(filter.irreversible?).to be true
|
expect(filter.irreversible?).to be irreversible
|
||||||
expect(filter.expires_at).to be_nil
|
expect(filter.expires_at).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with different parameters' do
|
||||||
|
let(:irreversible) { false }
|
||||||
|
let(:whole_word) { true }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a filter' do
|
||||||
|
filter = user.account.custom_filters.first
|
||||||
|
expect(filter).to_not be_nil
|
||||||
|
expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]]
|
||||||
|
expect(filter.context).to eq %w(home)
|
||||||
|
expect(filter.irreversible?).to be irreversible
|
||||||
|
expect(filter.expires_at).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe DeliveryFailureTracker do
|
||||||
|
|
||||||
describe '#track_failure!' do
|
describe '#track_failure!' do
|
||||||
it 'marks URL as unavailable after 7 days of being called' do
|
it 'marks URL as unavailable after 7 days of being called' do
|
||||||
6.times { |i| redis.sadd('exhausted_deliveries:example.com', i) }
|
6.times { |i| redis.sadd?('exhausted_deliveries:example.com', i) }
|
||||||
subject.track_failure!
|
subject.track_failure!
|
||||||
|
|
||||||
expect(subject.days).to eq 7
|
expect(subject.days).to eq 7
|
||||||
|
|
|
@ -11,7 +11,7 @@ RSpec.describe Vacuum::FeedsVacuum do
|
||||||
redis.zadd(feed_key_for(inactive_user), 1, 1)
|
redis.zadd(feed_key_for(inactive_user), 1, 1)
|
||||||
redis.zadd(feed_key_for(active_user), 1, 1)
|
redis.zadd(feed_key_for(active_user), 1, 1)
|
||||||
redis.zadd(feed_key_for(inactive_user, 'reblogs'), 2, 2)
|
redis.zadd(feed_key_for(inactive_user, 'reblogs'), 2, 2)
|
||||||
redis.sadd(feed_key_for(inactive_user, 'reblogs:2'), 3)
|
redis.sadd?(feed_key_for(inactive_user, 'reblogs:2'), 3)
|
||||||
|
|
||||||
subject.perform
|
subject.perform
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,48 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe AccountMigration, type: :model do
|
RSpec.describe AccountMigration, type: :model do
|
||||||
|
describe 'validations' do
|
||||||
|
let(:source_account) { Fabricate(:account) }
|
||||||
|
let(:target_acct) { target_account.acct }
|
||||||
|
|
||||||
|
let(:subject) { AccountMigration.new(account: source_account, acct: target_acct) }
|
||||||
|
|
||||||
|
context 'with valid properties' do
|
||||||
|
let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
target_account.aliases.create!(acct: source_account.acct)
|
||||||
|
|
||||||
|
service_double = double
|
||||||
|
allow(ResolveAccountService).to receive(:new).and_return(service_double)
|
||||||
|
allow(service_double).to receive(:call).with(target_acct, anything).and_return(target_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'passes validations' do
|
||||||
|
expect(subject).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unresolveable account' do
|
||||||
|
let(:target_acct) { 'target@remote' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
service_double = double
|
||||||
|
allow(ResolveAccountService).to receive(:new).and_return(service_double)
|
||||||
|
allow(service_double).to receive(:call).with(target_acct, anything).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has errors on acct field' do
|
||||||
|
expect(subject).to model_have_error_on_field(:acct)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a space in the domain part' do
|
||||||
|
let(:target_acct) { 'target@remote. org' }
|
||||||
|
|
||||||
|
it 'has errors on acct field' do
|
||||||
|
expect(subject).to model_have_error_on_field(:acct)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue