mirror of
https://git.bsd.gay/fef/nyastodon.git
synced 2024-12-26 05:33:43 +01:00
Merge branch 'master' into glitch-soc/merge-upstream
This commit is contained in:
commit
88a0395a58
22 changed files with 108 additions and 28 deletions
1
Gemfile
1
Gemfile
|
@ -10,6 +10,7 @@ gem 'rails', '~> 5.2.1'
|
||||||
|
|
||||||
gem 'hamlit-rails', '~> 0.2'
|
gem 'hamlit-rails', '~> 0.2'
|
||||||
gem 'pg', '~> 1.0'
|
gem 'pg', '~> 1.0'
|
||||||
|
gem 'makara', '~> 0.4'
|
||||||
gem 'pghero', '~> 2.1'
|
gem 'pghero', '~> 2.1'
|
||||||
gem 'dotenv-rails', '~> 2.2', '< 2.3'
|
gem 'dotenv-rails', '~> 2.2', '< 2.3'
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,8 @@ GEM
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.0)
|
mail (2.7.0)
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
|
makara (0.4.0)
|
||||||
|
activerecord (>= 3.0.0)
|
||||||
marcel (0.3.2)
|
marcel (0.3.2)
|
||||||
mimemagic (~> 0.3.2)
|
mimemagic (~> 0.3.2)
|
||||||
mario-redis-lock (1.2.1)
|
mario-redis-lock (1.2.1)
|
||||||
|
@ -700,6 +702,7 @@ DEPENDENCIES
|
||||||
letter_opener_web (~> 1.3)
|
letter_opener_web (~> 1.3)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.10)
|
lograge (~> 0.10)
|
||||||
|
makara (~> 0.4)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
microformats (~> 4.0)
|
microformats (~> 4.0)
|
||||||
|
|
|
@ -30,6 +30,12 @@ module Admin
|
||||||
redirect_to admin_invites_path
|
redirect_to admin_invites_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deactivate_all
|
||||||
|
authorize :invite, :deactivate_all?
|
||||||
|
Invite.available.in_batches.update_all(expires_at: Time.now.utc)
|
||||||
|
redirect_to admin_invites_path
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
|
|
|
@ -19,7 +19,7 @@ module StreamEntriesHelper
|
||||||
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')])
|
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')])
|
||||||
end
|
end
|
||||||
elsif current_account.following?(account) || current_account.requested?(account)
|
elsif current_account.following?(account) || current_account.requested?(account)
|
||||||
link_to account_unfollow_path(account), class: 'button logo-button', data: { method: :post } do
|
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
|
||||||
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
|
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -140,7 +140,7 @@ export function redraft(status) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function deleteStatus(id, withRedraft = false) {
|
export function deleteStatus(id, router, withRedraft = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const status = getState().getIn(['statuses', id]);
|
const status = getState().getIn(['statuses', id]);
|
||||||
|
|
||||||
|
@ -153,6 +153,10 @@ export function deleteStatus(id, withRedraft = false) {
|
||||||
|
|
||||||
if (withRedraft) {
|
if (withRedraft) {
|
||||||
dispatch(redraft(status));
|
dispatch(redraft(status));
|
||||||
|
|
||||||
|
if (!getState().getIn(['compose', 'mounted'])) {
|
||||||
|
router.push('/statuses/new');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(deleteStatusFail(id, error));
|
dispatch(deleteStatusFail(id, error));
|
||||||
|
|
|
@ -96,11 +96,11 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
this.props.onDelete(this.props.status);
|
this.props.onDelete(this.props.status, this.context.router.history);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRedraftClick = () => {
|
handleRedraftClick = () => {
|
||||||
this.props.onDelete(this.props.status, true);
|
this.props.onDelete(this.props.status, this.context.router.history, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePinClick = () => {
|
handlePinClick = () => {
|
||||||
|
|
|
@ -93,14 +93,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onDelete (status, withRedraft = false) {
|
onDelete (status, history, withRedraft = false) {
|
||||||
if (!deleteModal) {
|
if (!deleteModal) {
|
||||||
dispatch(deleteStatus(status.get('id'), withRedraft));
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||||
} else {
|
} else {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default class Upload extends ImmutablePureComponent {
|
||||||
onUndo: PropTypes.func.isRequired,
|
onUndo: PropTypes.func.isRequired,
|
||||||
onDescriptionChange: PropTypes.func.isRequired,
|
onDescriptionChange: PropTypes.func.isRequired,
|
||||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -28,6 +29,17 @@ export default class Upload extends ImmutablePureComponent {
|
||||||
dirtyDescription: null,
|
dirtyDescription: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||||
|
this.handleSubmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.handleInputBlur();
|
||||||
|
this.props.onSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
handleUndoClick = () => {
|
handleUndoClick = () => {
|
||||||
this.props.onUndo(this.props.media.get('id'));
|
this.props.onUndo(this.props.media.get('id'));
|
||||||
}
|
}
|
||||||
|
@ -93,6 +105,7 @@ export default class Upload extends ImmutablePureComponent {
|
||||||
onFocus={this.handleInputFocus}
|
onFocus={this.handleInputFocus}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
onBlur={this.handleInputBlur}
|
onBlur={this.handleInputBlur}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import Upload from '../components/upload';
|
import Upload from '../components/upload';
|
||||||
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
|
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
|
||||||
import { openModal } from '../../../actions/modal';
|
import { openModal } from '../../../actions/modal';
|
||||||
|
import { submitCompose } from '../../../actions/compose';
|
||||||
|
|
||||||
const mapStateToProps = (state, { id }) => ({
|
const mapStateToProps = (state, { id }) => ({
|
||||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||||
|
@ -21,6 +22,10 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(openModal('FOCAL_POINT', { id }));
|
dispatch(openModal('FOCAL_POINT', { id }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onSubmit () {
|
||||||
|
dispatch(submitCompose());
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Upload);
|
export default connect(mapStateToProps, mapDispatchToProps)(Upload);
|
||||||
|
|
|
@ -139,6 +139,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||||
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
|
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
|
||||||
|
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
|
||||||
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
|
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
|
||||||
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
|
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
|
||||||
<li><a href='https://github.com/tootsuite/documentation#documentation' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
|
<li><a href='https://github.com/tootsuite/documentation#documentation' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
|
||||||
|
|
|
@ -65,11 +65,11 @@ export default class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
this.props.onDelete(this.props.status);
|
this.props.onDelete(this.props.status, this.context.router.history);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRedraftClick = () => {
|
handleRedraftClick = () => {
|
||||||
this.props.onDelete(this.props.status, true);
|
this.props.onDelete(this.props.status, this.context.router.history, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDirectClick = () => {
|
handleDirectClick = () => {
|
||||||
|
|
|
@ -174,16 +174,16 @@ export default class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteClick = (status, withRedraft = false) => {
|
handleDeleteClick = (status, history, withRedraft = false) => {
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
|
|
||||||
if (!deleteModal) {
|
if (!deleteModal) {
|
||||||
dispatch(deleteStatus(status.get('id'), withRedraft));
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||||
} else {
|
} else {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)),
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,17 @@
|
||||||
transition: all 200ms ease-out;
|
transition: all 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--destructive {
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background-color: $error-red;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: $ui-primary-color;
|
background-color: $ui-primary-color;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
|
@ -110,6 +110,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.button--destructive {
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background: $error-red;
|
||||||
|
|
||||||
|
svg path:last-child {
|
||||||
|
fill: $error-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
svg {
|
svg {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -42,7 +42,14 @@ class User < ApplicationRecord
|
||||||
include Settings::Extend
|
include Settings::Extend
|
||||||
include Omniauthable
|
include Omniauthable
|
||||||
|
|
||||||
ACTIVE_DURATION = 7.days
|
# The home and list feeds will be stored in Redis for this amount
|
||||||
|
# of time, and status fan-out to followers will include only people
|
||||||
|
# within this time frame. Lowering the duration may improve performance
|
||||||
|
# if lots of people sign up, but not a lot of them check their feed
|
||||||
|
# every day. Raising the duration reduces the amount of expensive
|
||||||
|
# RegenerationWorker jobs that need to be run when those people come
|
||||||
|
# to check their feed
|
||||||
|
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days
|
||||||
|
|
||||||
devise :two_factor_authenticatable,
|
devise :two_factor_authenticatable,
|
||||||
otp_secret_encryption_key: Rails.configuration.x.otp_secret
|
otp_secret_encryption_key: Rails.configuration.x.otp_secret
|
||||||
|
|
|
@ -9,6 +9,10 @@ class InvitePolicy < ApplicationPolicy
|
||||||
min_required_role?
|
min_required_role?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deactivate_all?
|
||||||
|
admin?
|
||||||
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
owner? || (Setting.min_invite_role == 'admin' ? admin? : staff?)
|
owner? || (Setting.min_invite_role == 'admin' ? admin? : staff?)
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ProcessMentionsService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
next match if mention_undeliverable?(mentioned_account)
|
next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended
|
||||||
|
|
||||||
mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status)
|
mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status)
|
||||||
|
|
||||||
|
|
|
@ -9,22 +9,28 @@
|
||||||
%li= filter_link_to t('admin.invites.filter.available'), available: 1, expired: nil
|
%li= filter_link_to t('admin.invites.filter.available'), available: 1, expired: nil
|
||||||
%li= filter_link_to t('admin.invites.filter.expired'), available: nil, expired: 1
|
%li= filter_link_to t('admin.invites.filter.expired'), available: nil, expired: 1
|
||||||
|
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
- if policy(:invite).create?
|
- if policy(:invite).create?
|
||||||
%p= t('invites.prompt')
|
%p= t('invites.prompt')
|
||||||
|
|
||||||
= render 'invites/form'
|
= render 'invites/form'
|
||||||
|
|
||||||
%hr/
|
%hr.spacer/
|
||||||
|
|
||||||
%table.table
|
.table-wrapper
|
||||||
%thead
|
%table.table
|
||||||
%tr
|
%thead
|
||||||
%th
|
%tr
|
||||||
%th= t('invites.table.uses')
|
%th
|
||||||
%th= t('invites.table.expires_at')
|
%th= t('invites.table.uses')
|
||||||
%th
|
%th= t('invites.table.expires_at')
|
||||||
%th
|
%th
|
||||||
%tbody
|
%th
|
||||||
= render @invites
|
%tbody
|
||||||
|
= render @invites
|
||||||
|
|
||||||
= paginate @invites
|
= paginate @invites
|
||||||
|
|
||||||
|
- if policy(:invite).deactivate_all?
|
||||||
|
= link_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
|
||||||
|
|
|
@ -42,6 +42,6 @@
|
||||||
%h4= t 'footer.more'
|
%h4= t 'footer.more'
|
||||||
%ul
|
%ul
|
||||||
%li= link_to t('about.source_code'), Mastodon::Version.source_url
|
%li= link_to t('about.source_code'), Mastodon::Version.source_url
|
||||||
%li= link_to 'joinmastodon.org', 'https://joinmastodon.org'
|
%li= link_to t('about.apps'), 'https://joinmastodon.org/apps'
|
||||||
|
|
||||||
= render template: 'layouts/application'
|
= render template: 'layouts/application'
|
||||||
|
|
|
@ -6,6 +6,7 @@ en:
|
||||||
about_this: About
|
about_this: About
|
||||||
administered_by: 'Administered by:'
|
administered_by: 'Administered by:'
|
||||||
api: API
|
api: API
|
||||||
|
apps: Mobile apps
|
||||||
closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there.
|
closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there.
|
||||||
contact: Contact
|
contact: Contact
|
||||||
contact_missing: Not set
|
contact_missing: Not set
|
||||||
|
@ -281,6 +282,7 @@ en:
|
||||||
search: Search
|
search: Search
|
||||||
title: Known instances
|
title: Known instances
|
||||||
invites:
|
invites:
|
||||||
|
deactivate_all: Deactivate all
|
||||||
filter:
|
filter:
|
||||||
all: All
|
all: All
|
||||||
available: Available
|
available: Available
|
||||||
|
|
|
@ -137,7 +137,12 @@ Rails.application.routes.draw do
|
||||||
resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
|
resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
|
||||||
resources :action_logs, only: [:index]
|
resources :action_logs, only: [:index]
|
||||||
resource :settings, only: [:edit, :update]
|
resource :settings, only: [:edit, :update]
|
||||||
resources :invites, only: [:index, :create, :destroy]
|
|
||||||
|
resources :invites, only: [:index, :create, :destroy] do
|
||||||
|
collection do
|
||||||
|
post :deactivate_all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :relays, only: [:index, :new, :create, :destroy] do
|
resources :relays, only: [:index, :new, :create, :destroy] do
|
||||||
member do
|
member do
|
||||||
|
|
|
@ -3,7 +3,7 @@ class CopyStatusStats < ActiveRecord::Migration[5.2]
|
||||||
|
|
||||||
def up
|
def up
|
||||||
safety_assured do
|
safety_assured do
|
||||||
Status.where.not(id: StatusStat.select('status_id')).select('id').find_in_batches do |statuses|
|
Status.unscoped.select('id').find_in_batches(batch_size: 5_000) do |statuses|
|
||||||
execute <<-SQL.squish
|
execute <<-SQL.squish
|
||||||
INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at)
|
INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at)
|
||||||
SELECT id, reblogs_count, favourites_count, created_at, updated_at
|
SELECT id, reblogs_count, favourites_count, created_at, updated_at
|
||||||
|
|
Loading…
Reference in a new issue