Merge remote-tracking branch 'origin/master' into merge-upstream

Conflicts:
	app/javascript/styles/mastodon/components.scss
	app/javascript/styles/mastodon/modal.scss
This commit is contained in:
David Yip 2018-01-19 15:22:10 -06:00
commit 2ca965c704
No known key found for this signature in database
GPG key ID: 7DA0036508FCC0CC
90 changed files with 375 additions and 496 deletions

View file

@ -1 +1 @@
2.4.2
2.5.0

View file

@ -40,20 +40,20 @@ addons:
- yarn
rvm:
- 2.3.4
- 2.4.2
- 2.5.0
services:
- redis-server
install:
- gem update --system
- nvm install
- bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16
- yarn install
before_script:
- bundle exec rake parallel:create parallel:load_schema parallel:prepare
- bundle exec rails assets:precompile
- ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
script:

View file

@ -1,4 +1,4 @@
FROM ruby:2.4.2-alpine3.6
FROM ruby:2.5.0-alpine3.7
LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server"
@ -40,6 +40,7 @@ RUN apk -U upgrade \
protobuf \
su-exec \
tini \
tzdata \
&& update-ca-certificates \
&& mkdir -p /tmp/src /opt \
&& wget -O yarn.tar.gz "https://github.com/yarnpkg/yarn/releases/download/v$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.3.0', '< 2.5.0'
ruby '>= 2.3.0', '< 2.6.0'
gem 'pkg-config', '~> 1.2'
@ -29,7 +29,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639'
gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.3'
gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0'
gem 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0'

View file

@ -110,7 +110,7 @@ GEM
activesupport
charlock_holmes (0.7.5)
chunky_png (1.3.8)
cld3 (3.2.1)
cld3 (3.2.2)
ffi (>= 1.1.0, < 1.10.0)
climate_control (0.2.0)
cocaine (0.5.8)
@ -125,7 +125,7 @@ GEM
css_parser (1.6.0)
addressable
debug_inspector (0.0.3)
devise (4.3.0)
devise (4.4.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2)
@ -569,7 +569,7 @@ DEPENDENCIES
charlock_holmes (~> 0.7.5)
cld3 (~> 3.2.0)
climate_control (~> 0.2)
devise (~> 4.3)
devise (~> 4.4)
devise-two-factor (~> 3.0)
doorkeeper (~> 4.2)
dotenv-rails (~> 2.2)
@ -651,7 +651,7 @@ DEPENDENCIES
webpush
RUBY VERSION
ruby 2.4.2p198
ruby 2.5.0p0
BUNDLED WITH
1.16.1

View file

@ -32,7 +32,7 @@ module UserTrackingConcern
end
def regenerate_feed!
Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) == 1 && Redis.current.expire("account:#{current_user.account_id}:regeneration", 3_600 * 24)
Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) && Redis.current.expire("account:#{current_user.account_id}:regeneration", 1.day.seconds)
RegenerationWorker.perform_async(current_user.account_id)
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1,14 +0,0 @@
import { openModal } from './modal';
import { changeSetting, saveSettings } from './settings';
export function showOnboardingOnce() {
return (dispatch, getState) => {
const alreadySeen = getState().getIn(['settings', 'onboarded']);
if (!alreadySeen) {
dispatch(openModal('ONBOARDING'));
dispatch(changeSetting(['onboarded'], true));
dispatch(saveSettings());
}
};
};

View file

@ -2,7 +2,6 @@ import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import configureStore from '../store/configureStore';
import { showOnboardingOnce } from '../actions/onboarding';
import { BrowserRouter, Route } from 'react-router-dom';
import { ScrollContext } from 'react-router-scroll-4';
import UI from '../features/ui';
@ -40,8 +39,6 @@ export default class Mastodon extends React.PureComponent {
const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s';
window.setTimeout(() => navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'), 5 * 60 * 1000);
}
store.dispatch(showOnboardingOnce());
}
componentWillUnmount () {

View file

@ -9,7 +9,6 @@ import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal';
import {
OnboardingModal,
MuteModal,
ReportModal,
EmbedModal,
@ -18,7 +17,6 @@ import {
const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
'ONBOARDING': OnboardingModal,
'VIDEO': () => Promise.resolve({ default: VideoModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),

View file

@ -1,318 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import Permalink from '../../../components/permalink';
import ComposeForm from '../../compose/components/compose_form';
import Search from '../../compose/components/search';
import NavigationBar from '../../compose/components/navigation_bar';
import ColumnHeader from './column_header';
import { List as ImmutableList } from 'immutable';
import { me } from '../../../initial_state';
const noop = () => { };
const messages = defineMessages({
home_title: { id: 'column.home', defaultMessage: 'Home' },
notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' },
});
const PageOne = ({ acct, domain }) => (
<div className='onboarding-modal__page onboarding-modal__page-one'>
<div style={{ flex: '0 0 auto' }}>
<div className='onboarding-modal__page-one__elephant-friend' />
</div>
<div>
<h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
<p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
<p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p>
</div>
</div>
);
PageOne.propTypes = {
acct: PropTypes.string.isRequired,
domain: PropTypes.string.isRequired,
};
const PageTwo = ({ myAccount }) => (
<div className='onboarding-modal__page onboarding-modal__page-two'>
<div className='figure non-interactive'>
<div className='pseudo-drawer'>
<NavigationBar account={myAccount} />
</div>
<ComposeForm
text='Awoo! #introductions'
suggestions={ImmutableList()}
mentionedDomains={[]}
spoiler={false}
onChange={noop}
onSubmit={noop}
onPaste={noop}
onPickEmoji={noop}
onChangeSpoilerText={noop}
onClearSuggestions={noop}
onFetchSuggestions={noop}
onSuggestionSelected={noop}
showSearch
/>
</div>
<p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
</div>
);
PageTwo.propTypes = {
myAccount: ImmutablePropTypes.map.isRequired,
};
const PageThree = ({ myAccount }) => (
<div className='onboarding-modal__page onboarding-modal__page-three'>
<div className='figure non-interactive'>
<Search
value=''
onChange={noop}
onSubmit={noop}
onClear={noop}
onShow={noop}
/>
<div className='pseudo-drawer'>
<NavigationBar account={myAccount} />
</div>
</div>
<p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }} /></p>
<p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
</div>
);
PageThree.propTypes = {
myAccount: ImmutablePropTypes.map.isRequired,
};
const PageFour = ({ domain, intl }) => (
<div className='onboarding-modal__page onboarding-modal__page-four'>
<div className='onboarding-modal__page-four__columns'>
<div className='row'>
<div>
<div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
<p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.' /></p>
</div>
<div>
<div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
<p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
</div>
</div>
<div className='row'>
<div>
<div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
</div>
<div>
<div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
</div>
</div>
<p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
</div>
</div>
);
PageFour.propTypes = {
domain: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
};
const PageSix = ({ admin, domain }) => {
let adminSection = '';
if (admin) {
adminSection = (
<p>
<FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
<br />
<FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{ domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }} />
</p>
);
}
return (
<div className='onboarding-modal__page onboarding-modal__page-six'>
<h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
{adminSection}
<p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
<p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
<p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
</div>
);
};
PageSix.propTypes = {
admin: ImmutablePropTypes.map,
domain: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
myAccount: state.getIn(['accounts', me]),
admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
domain: state.getIn(['meta', 'domain']),
});
@connect(mapStateToProps)
@injectIntl
export default class OnboardingModal extends React.PureComponent {
static propTypes = {
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.map.isRequired,
domain: PropTypes.string.isRequired,
admin: ImmutablePropTypes.map,
};
state = {
currentIndex: 0,
};
componentWillMount() {
const { myAccount, admin, domain, intl } = this.props;
this.pages = [
<PageOne acct={myAccount.get('acct')} domain={domain} />,
<PageTwo myAccount={myAccount} />,
<PageThree myAccount={myAccount} />,
<PageFour domain={domain} intl={intl} />,
<PageSix admin={admin} domain={domain} />,
];
};
componentDidMount() {
window.addEventListener('keyup', this.handleKeyUp);
}
componentWillUnmount() {
window.addEventListener('keyup', this.handleKeyUp);
}
handleSkip = (e) => {
e.preventDefault();
this.props.onClose();
}
handleDot = (e) => {
const i = Number(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.setState({ currentIndex: i });
}
handlePrev = () => {
this.setState(({ currentIndex }) => ({
currentIndex: Math.max(0, currentIndex - 1),
}));
}
handleNext = () => {
const { pages } = this;
this.setState(({ currentIndex }) => ({
currentIndex: Math.min(currentIndex + 1, pages.length - 1),
}));
}
handleSwipe = (index) => {
this.setState({ currentIndex: index });
}
handleKeyUp = ({ key }) => {
switch (key) {
case 'ArrowLeft':
this.handlePrev();
break;
case 'ArrowRight':
this.handleNext();
break;
}
}
handleClose = () => {
this.props.onClose();
}
render () {
const { pages } = this;
const { currentIndex } = this.state;
const hasMore = currentIndex < pages.length - 1;
const nextOrDoneBtn = hasMore ? (
<button
onClick={this.handleNext}
className='onboarding-modal__nav onboarding-modal__next'
>
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
</button>
) : (
<button
onClick={this.handleClose}
className='onboarding-modal__nav onboarding-modal__done'
>
<FormattedMessage id='onboarding.done' defaultMessage='Done' />
</button>
);
return (
<div className='modal-root__modal onboarding-modal'>
<ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'>
{pages.map((page, i) => {
const className = classNames('onboarding-modal__page__wrapper', {
'onboarding-modal__page__wrapper--active': i === currentIndex,
});
return (
<div key={i} className={className}>{page}</div>
);
})}
</ReactSwipeableViews>
<div className='onboarding-modal__paginator'>
<div>
<button
onClick={this.handleSkip}
className='onboarding-modal__nav onboarding-modal__skip'
>
<FormattedMessage id='onboarding.skip' defaultMessage='Skip' />
</button>
</div>
<div className='onboarding-modal__dots'>
{pages.map((_, i) => {
const className = classNames('onboarding-modal__dot', {
active: i === currentIndex,
});
return (
<div
key={`dot-${i}`}
role='button'
tabIndex='0'
data-index={i}
onClick={this.handleDot}
className={className}
/>
);
})}
</div>
<div>
{nextOrDoneBtn}
</div>
</div>
</div>
);
}
}

View file

@ -94,10 +94,6 @@ export function Mutes () {
return import(/* webpackChunkName: "features/mutes" */'../../mutes');
}
export function OnboardingModal () {
return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal');
}
export function MuteModal () {
return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
}

View file

@ -40,7 +40,7 @@ const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
mMap.set('loaded', true);
mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : oldIds.concat(ids));
mMap.set('isPartial', isPartial);
}));
};

View file

@ -6,7 +6,6 @@
@import 'mastodon/reset';
@import 'mastodon/basics';
@import 'mastodon/modal';
@import 'mastodon/containers';
@import 'mastodon/lists';
@import 'mastodon/footer';
@ -15,7 +14,9 @@
@import 'mastodon/forms';
@import 'mastodon/accounts';
@import 'mastodon/stream_entries';
@import 'mastodon/boost';
@import 'mastodon/components';
@import 'mastodon/modal';
@import 'mastodon/emoji_picker';
@import 'mastodon/about';
@import 'mastodon/tables';

View file

@ -228,6 +228,13 @@ h3 {
line-height: 25px;
}
h5 {
font-size: 16px;
line-height: 21px;
font-weight: 700;
color: lighten($ui-base-color, 34%);
}
.input {
td {
background: darken($ui-base-color, 8%);
@ -356,6 +363,19 @@ h3 {
font-weight: 500 !important;
}
}
&.button-small {
td {
border-radius: 4px;
font-size: 14px;
padding: 8px 16px;
a {
padding: 5px 16px !important;
line-height: 26px !important;
}
}
}
}
.button-default {
@ -379,6 +399,14 @@ h3 {
padding-right: 16px;
}
.padded-bottom {
padding-bottom: 32px;
}
.margin-bottom {
margin-bottom: 20px;
}
.hero-icon {
width: 64px;
@ -463,6 +491,22 @@ h3 {
border-top: 1px solid lighten($ui-base-color, 8%);
}
ul {
padding-left: 15px;
margin-top: 0;
margin-bottom: 0;
padding-top: 16px;
li {
margin-bottom: 16px;
color: lighten($ui-base-color, 26%);
span {
color: $ui-primary-color;
}
}
}
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) {
body {
min-height: 1024px !important;

View file

@ -1762,7 +1762,7 @@
position: absolute;
top: 0;
left: 0;
background: lighten($ui-base-color, 13%) url('~images/wave-drawer.png') no-repeat bottom / 100% auto;
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
box-sizing: border-box;
padding: 0;
display: flex;
@ -1777,7 +1777,7 @@
}
> .mastodon {
background: url('~images/mastodon-ui.png') no-repeat left bottom / contain;
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
flex: 1;
}
}
@ -2154,10 +2154,7 @@
}
}
@import 'boost';
.no-reduce-motion button.icon-button i.fa-retweet {
background-position: 0 0;
height: 19px;
transition: background-position 0.9s steps(10);
@ -3303,7 +3300,6 @@
z-index: 100;
}
.onboarding-modal,
.error-modal,
.embed-modal {
background: $ui-secondary-color;
@ -3314,26 +3310,6 @@
flex-direction: column;
}
.onboarding-modal__pager {
height: 80vh;
width: 80vw;
max-width: 520px;
max-height: 420px;
.react-swipeable-view-container > div {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 25px;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
display: flex;
user-select: text;
}
}
.error-modal__body {
height: 80vh;
width: 80vw;
@ -3367,23 +3343,6 @@
text-align: center;
}
@media screen and (max-width: 550px) {
.onboarding-modal {
width: 100%;
height: 100%;
border-radius: 0;
}
.onboarding-modal__pager {
width: 100%;
height: auto;
max-width: none;
max-height: none;
flex: 1 1 auto;
}
}
.onboarding-modal__paginator,
.error-modal__footer {
flex: 0 0 auto;
background: darken($ui-secondary-color, 8%);
@ -3394,7 +3353,6 @@
min-width: 33px;
}
.onboarding-modal__nav,
.error-modal__nav {
color: darken($ui-secondary-color, 34%);
background-color: transparent;
@ -3410,11 +3368,6 @@
&:active {
color: darken($ui-secondary-color, 38%);
}
&.onboarding-modal__done,
&.onboarding-modal__next {
color: $ui-highlight-color;
}
}
}
@ -3422,6 +3375,7 @@
justify-content: center;
}
<<<<<<< HEAD
.onboarding-modal__dots {
flex: 1 1 auto;
display: flex;
@ -3632,6 +3586,8 @@
margin-left: 10px;
}
=======
>>>>>>> origin/master
.boost-modal,
.confirmation-modal,
.report-modal,

View file

@ -1,5 +1,5 @@
.modal-layout {
background: $ui-base-color url('~images/wave-modal.png') repeat-x bottom fixed;
background: $ui-base-color url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-lighter-color)}"/></svg>') repeat-x bottom fixed;
display: flex;
flex-direction: column;
height: 100vh;
@ -15,6 +15,6 @@
> * {
flex: 1;
max-height: 235px;
background: url('~images/mastodon-ui.png') no-repeat left bottom / contain;
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
}
}

View file

@ -69,12 +69,13 @@ class ActivityPub::Activity
def distribute(status)
crawl_links(status)
notify_about_reblog(status) if reblog_of_local_account?(status)
notify_about_mentions(status)
# Only continue if the status is supposed to have
# arrived in real-time
return unless @options[:override_timestamps]
notify_about_reblog(status) if reblog_of_local_account?(status)
notify_about_mentions(status)
distribute_to_followers(status)
end

View file

@ -54,4 +54,15 @@ class UserMailer < Devise::Mailer
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
end
end
def welcome(user)
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
end
end
end

View file

@ -104,7 +104,7 @@ class Account < ApplicationRecord
has_many :lists, through: :list_accounts
# Account migrations
belongs_to :moved_to_account, class_name: 'Account'
belongs_to :moved_to_account, class_name: 'Account', optional: true
scope :remote, -> { where.not(domain: nil) }
scope :local, -> { where(domain: nil) }

View file

@ -13,7 +13,7 @@
class AccountDomainBlock < ApplicationRecord
include Paginable
belongs_to :account, required: true
belongs_to :account
validates :domain, presence: true, uniqueness: { scope: :account_id }
after_create :remove_blocking_cache

View file

@ -16,8 +16,8 @@
class Admin::ActionLog < ApplicationRecord
serialize :recorded_changes
belongs_to :account, required: true
belongs_to :target, required: true, polymorphic: true
belongs_to :account
belongs_to :target, polymorphic: true
default_scope -> { order('id desc') }

View file

@ -13,8 +13,8 @@
class Block < ApplicationRecord
include Paginable
belongs_to :account, required: true
belongs_to :target_account, class_name: 'Account', required: true
belongs_to :account
belongs_to :target_account, class_name: 'Account'
validates :account_id, uniqueness: { scope: :target_account_id }

View file

@ -9,6 +9,6 @@
#
class ConversationMute < ApplicationRecord
belongs_to :account, required: true
belongs_to :conversation, required: true
belongs_to :account
belongs_to :conversation
end

View file

@ -13,8 +13,8 @@
class Favourite < ApplicationRecord
include Paginable
belongs_to :account, inverse_of: :favourites, required: true
belongs_to :status, inverse_of: :favourites, counter_cache: true, required: true
belongs_to :account, inverse_of: :favourites
belongs_to :status, inverse_of: :favourites, counter_cache: true
has_one :notification, as: :activity, dependent: :destroy

View file

@ -14,12 +14,11 @@
class Follow < ApplicationRecord
include Paginable
belongs_to :account, counter_cache: :following_count, required: true
belongs_to :account, counter_cache: :following_count
belongs_to :target_account,
class_name: 'Account',
counter_cache: :followers_count,
required: true
counter_cache: :followers_count
has_one :notification, as: :activity, dependent: :destroy

View file

@ -14,8 +14,8 @@
class FollowRequest < ApplicationRecord
include Paginable
belongs_to :account, required: true
belongs_to :target_account, class_name: 'Account', required: true
belongs_to :account
belongs_to :target_account, class_name: 'Account'
has_one :notification, as: :activity, dependent: :destroy

View file

@ -20,7 +20,7 @@ class Import < ApplicationRecord
self.inheritance_column = false
belongs_to :account, required: true
belongs_to :account
enum type: [:following, :blocking, :muting]

View file

@ -14,7 +14,7 @@
#
class Invite < ApplicationRecord
belongs_to :user, required: true
belongs_to :user
has_many :users, inverse_of: :invite
scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }

View file

@ -15,7 +15,7 @@ class List < ApplicationRecord
PER_ACCOUNT_LIMIT = 50
belongs_to :account
belongs_to :account, optional: true
has_many :list_accounts, inverse_of: :list, dependent: :destroy
has_many :accounts, through: :list_accounts

View file

@ -10,9 +10,9 @@
#
class ListAccount < ApplicationRecord
belongs_to :list, required: true
belongs_to :account, required: true
belongs_to :follow, required: true
belongs_to :list
belongs_to :account
belongs_to :follow
validates :account_id, uniqueness: { scope: :list_id }

View file

@ -62,8 +62,8 @@ class MediaAttachment < ApplicationRecord
},
}.freeze
belongs_to :account, inverse_of: :media_attachments
belongs_to :status, inverse_of: :media_attachments
belongs_to :account, inverse_of: :media_attachments, optional: true
belongs_to :status, inverse_of: :media_attachments, optional: true
has_attached_file :file,
styles: ->(f) { file_styles f },

View file

@ -11,8 +11,8 @@
#
class Mention < ApplicationRecord
belongs_to :account, inverse_of: :mentions, required: true
belongs_to :status, required: true
belongs_to :account, inverse_of: :mentions
belongs_to :status
has_one :notification, as: :activity, dependent: :destroy

View file

@ -14,8 +14,8 @@
class Mute < ApplicationRecord
include Paginable
belongs_to :account, required: true
belongs_to :target_account, class_name: 'Account', required: true
belongs_to :account
belongs_to :target_account, class_name: 'Account'
validates :account_id, uniqueness: { scope: :target_account_id }

View file

@ -26,15 +26,15 @@ class Notification < ApplicationRecord
STATUS_INCLUDES = [:account, :application, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :application, :media_attachments, :tags, mentions: :account]].freeze
belongs_to :account
belongs_to :from_account, class_name: 'Account'
belongs_to :activity, polymorphic: true
belongs_to :account, optional: true
belongs_to :from_account, class_name: 'Account', optional: true
belongs_to :activity, polymorphic: true, optional: true
belongs_to :mention, foreign_type: 'Mention', foreign_key: 'activity_id'
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id'
belongs_to :follow, foreign_type: 'Follow', foreign_key: 'activity_id'
belongs_to :follow_request, foreign_type: 'FollowRequest', foreign_key: 'activity_id'
belongs_to :favourite, foreign_type: 'Favourite', foreign_key: 'activity_id'
belongs_to :mention, foreign_type: 'Mention', foreign_key: 'activity_id', optional: true
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', optional: true
belongs_to :follow, foreign_type: 'Follow', foreign_key: 'activity_id', optional: true
belongs_to :follow_request, foreign_type: 'FollowRequest', foreign_key: 'activity_id', optional: true
belongs_to :favourite, foreign_type: 'Favourite', foreign_key: 'activity_id', optional: true
validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }

View file

@ -17,7 +17,7 @@
class Report < ApplicationRecord
belongs_to :account
belongs_to :target_account, class_name: 'Account'
belongs_to :action_taken_by_account, class_name: 'Account'
belongs_to :action_taken_by_account, class_name: 'Account', optional: true
scope :unresolved, -> { where(action_taken: false) }
scope :resolved, -> { where(action_taken: true) }

View file

@ -15,9 +15,9 @@
#
class SessionActivation < ApplicationRecord
belongs_to :user, inverse_of: :session_activations, required: true
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy
belongs_to :user, inverse_of: :session_activations
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy, optional: true
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy, optional: true
delegate :token,
to: :access_token,

View file

@ -34,14 +34,14 @@ class Status < ApplicationRecord
enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
belongs_to :application, class_name: 'Doorkeeper::Application'
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
belongs_to :account, inverse_of: :statuses, counter_cache: true, required: true
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
belongs_to :conversation
belongs_to :account, inverse_of: :statuses, counter_cache: true
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true
belongs_to :conversation, optional: true
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count, optional: true
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy

View file

@ -11,8 +11,8 @@
#
class StatusPin < ApplicationRecord
belongs_to :account, required: true
belongs_to :status, required: true
belongs_to :account
belongs_to :status
validates_with StatusPinValidator
end

View file

@ -19,7 +19,7 @@ class Subscription < ApplicationRecord
MIN_EXPIRATION = 1.day.to_i
MAX_EXPIRATION = 30.days.to_i
belongs_to :account, required: true
belongs_to :account
validates :callback_url, presence: true
validates :callback_url, uniqueness: { scope: :account_id }

View file

@ -50,8 +50,8 @@ class User < ApplicationRecord
devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
:confirmable
belongs_to :account, inverse_of: :user, required: true
belongs_to :invite, counter_cache: :uses
belongs_to :account, inverse_of: :user
belongs_to :invite, counter_cache: :uses, optional: true
accepts_nested_attributes_for :account
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
@ -223,5 +223,6 @@ class User < ApplicationRecord
def update_statistics!
BootstrapTimelineWorker.perform_async(account_id)
ActivityTracker.increment('activity:accounts:local')
UserMailer.welcome(self).deliver_later
end
end

View file

@ -1,5 +1,5 @@
<%= yield %>
---
<%= t('application_mailer.signature', instance: site_hostname) %>
<%= t 'about.hosted_on', domain: site_hostname %>
<%= t('application_mailer.settings', link: settings_preferences_url) %>

View file

@ -0,0 +1,146 @@
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell.hero
.email-row
.col-6
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.text-center.padded
%table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td
= image_tag full_pack_url('icon_done.svg'), alt: ''
%h1= t 'user_mailer.welcome.title', name: @resource.account.username
%p.lead= t 'user_mailer.welcome.explanation'
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell
.email-row
.col-3
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.input-cell.text-center.padded-bottom
%h5= t 'user_mailer.welcome.full_handle'
%table.input{ align: 'center', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td= "@#{@resource.account.username}@#{@instance}"
.col-3
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.content-start
%p= t 'user_mailer.welcome.full_handle_hint', instance: @instance
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell.content-start.border-top
.email-row
.col-4
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.padded
= t 'user_mailer.welcome.edit_profile_step'
.col-2
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell
%table.button.button-small{ align: 'left', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.button-primary
= link_to settings_profile_url do
%span= t 'user_mailer.welcome.edit_profile_action'
%tr
%td.content-cell
.email-row
.col-4
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.padded
= t 'user_mailer.welcome.review_preferences_step'
.col-2
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell
%table.button.button-small{ align: 'left', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.button-primary
= link_to settings_preferences_url do
%span= t 'user_mailer.welcome.review_preferences_action'
%tr
%td.content-cell.padded-bottom
.email-row
.col-4
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.padded
= t 'user_mailer.welcome.final_step'
.col-2
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell
%table.button.button-small{ align: 'left', cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.button-primary
= link_to web_url do
%span= t 'user_mailer.welcome.final_action'
%table.email-table{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.email-body
.email-container
%table.content-section{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.content-cell.border-top
.email-row
.col-6
%table.column{ cellspacing: 0, cellpadding: 0 }
%tbody
%tr
%td.column-cell.padded
%h5= t 'user_mailer.welcome.tips'
%ul
%li
%span= t 'user_mailer.welcome.tip_mobile_webapp'
%li
%span= t 'user_mailer.welcome.tip_bridge_html', bridge_url: 'https://bridge.joinmastodon.org'
%li
%span= t 'user_mailer.welcome.tip_following'
%li
%span= t 'user_mailer.welcome.tip_local_timeline', instance: @instance
%li
%span= t 'user_mailer.welcome.tip_federated_timeline'

View file

@ -0,0 +1,30 @@
<%= t 'user_mailer.welcome.title', name: @resource.account.username %> <%= t 'user_mailer.welcome.explanation' %>
===
<%= t 'user_mailer.welcome.full_handle' %> (<%= "@#{@resource.account.username}@#{@instance}" %>)
<%= t 'user_mailer.welcome.full_handle_hint', instance: @instance %>
---
<%= t 'user_mailer.welcome.edit_profile_step' %>
=> <%= settings_profile_url %>
<%= t 'user_mailer.welcome.review_preferences_step' %>
=> <%= settings_preferences_url %>
<%= t 'user_mailer.welcome.final_step' %>
=> <%= web_url %>
---
<%= t 'user_mailer.welcome.tips' %>
* <%= t 'user_mailer.welcome.tip_mobile_webapp' %>
* <%= strip_tags(t('user_mailer.welcome.tip_bridge_html')) %> (https://bridge.joinmastodon.org)
* <%= t 'user_mailer.welcome.tip_following' %>
* <%= t 'user_mailer.welcome.tip_local_timeline', instance: @instance %>
* <%= t 'user_mailer.welcome.tip_federated_timeline' %>

View file

@ -3,7 +3,7 @@
class ActivityPub::DeliveryWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: 8, dead: false
sidekiq_options queue: 'push', retry: 16, dead: false
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze

View file

@ -19,6 +19,9 @@ require_relative '../lib/mastodon/redis_config'
module Mastodon
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.1
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.

View file

@ -140,7 +140,6 @@ ar:
application_mailer:
salutation: "%{name}،"
settings: 'تغيير تفضيلات البريد الإلكتروني : %{link}'
signature: إشعارات ماستدون من %{instance}
view: 'View:'
applications:
created: تم إنشاء التطبيق بنجاح

View file

@ -26,7 +26,6 @@ bg:
unfollow: Не следвай
application_mailer:
settings: 'Промяна на предпочитанията за e-mail: %{link}'
signature: Mastodon известия от %{instance}
view: 'Преглед:'
applications:
invalid_url: Предоставеният URL е невалиден

View file

@ -340,7 +340,6 @@ ca:
application_mailer:
salutation: "%{name},"
settings: 'Canvia les preferències de correu: %{link}'
signature: Notificacions de Mastodon des de %{instance}
view: 'Vista:'
applications:
created: L'aplicació s'ha creat correctament

View file

@ -321,7 +321,6 @@ de:
application_mailer:
salutation: "%{name},"
settings: 'E-Mail-Einstellungen ändern: %{link}'
signature: Mastodon-Benachrichtigungen von %{instance}
view: 'Ansehen:'
applications:
created: Anwendung erstellt

View file

@ -341,7 +341,6 @@ en:
notification_preferences: Change e-mail preferences
salutation: "%{name},"
settings: 'Change e-mail preferences: %{link}'
signature: Mastodon notifications from %{instance}
view: 'View:'
view_profile: View Profile
view_status: View status
@ -734,6 +733,25 @@ en:
recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. <strong>Keep the recovery codes safe</strong>. For example, you may print them and store them with other important documents.
setup: Set up
wrong_code: The entered code was invalid! Are server time and device time correct?
user_mailer:
welcome:
edit_profile_action: Setup profile
edit_profile_step: You can customize your profile by uploading an avatar, header, changing your display name and more. If youd like to review new followers before theyre allowed to follow you, you can lock your account.
explanation: Here are some tips to get you started
final_action: Start posting
final_step: 'Start posting! Even without followers your public messages may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.'
full_handle: Your full handle
full_handle_hint: This is what you would tell your friends so they can message or follow you from another instance.
review_preferences_action: Change preferences
review_preferences_step: Make sure to set your preferences, such as which emails you'd like to receive, or what privacy level youd like your posts to default to. If you dont have motion sickness, you could choose to enable GIF autoplay.
subject: Welcome to Mastodon
tip_bridge_html: If you are coming from Twitter, you can find your friends on Mastodon by using the <a href="%{bridge_url}">bridge app</a>. It only works if they also used the bridge app though!
tip_federated_timeline: The federated timeline is a firehose view of the Mastodon network. But it only includes people your neighbours are subscribed to, so it's not complete.
tip_following: You follow your server's admin(s) by default. To find more interesting people, check the local and federated timelines.
tip_local_timeline: The local timeline is a firehose view of people on %{instance}. These are your immediate neighbours!
tip_mobile_webapp: If your mobile browser offers you to add Mastodon to your homescreen, you can receive push notifications. It acts like a native app in many ways!
tips: Tips
title: Welcome aboard, %{name}!
users:
invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code

View file

@ -237,7 +237,6 @@ eo:
subject: Nova raporto por %{instance} (#%{id})
application_mailer:
settings: 'Ŝanĝi la retpoŝt-mesaĝajn preferojn: %{link}'
signature: Sciigoj de Mastodon el %{instance}
view: 'Vidi:'
applications:
created: Aplikaĵo sukcesa kreis

View file

@ -332,7 +332,6 @@ es:
application_mailer:
salutation: "%{name},"
settings: 'Cambiar preferencias de correo: %{link}'
signature: Notificaciones de Mastodon desde %{instance}
view: 'Vista:'
applications:
created: Aplicación creada exitosamente

View file

@ -334,7 +334,6 @@ fa:
application_mailer:
salutation: "%{name}،"
settings: 'تغییر تنظیمات ایمیل: %{link}'
signature: اعلان‌های ماستدون از %{instance}
view: 'نمایش:'
applications:
created: برنامه با موفقیت ساخته شد

View file

@ -25,7 +25,6 @@ fi:
unfollow: Lopeta seuraaminen
application_mailer:
settings: 'Muokkaa sähköpostiasetuksia: %{link}'
signature: Mastodon-ilmoituksia palvelimelta %{instance}
view: 'Katso:'
applications:
invalid_url: Annettu URL on väärä

View file

@ -334,7 +334,6 @@ fr:
application_mailer:
salutation: "%{name},"
settings: 'Changer les préférences courriel: %{link}'
signature: Notifications de Mastodon depuis %{instance}
view: 'Voir :'
applications:
created: Application créée avec succès

View file

@ -340,7 +340,6 @@ gl:
application_mailer:
salutation: "%{name},"
settings: 'Mudar as preferencias de e-mail: %{link}'
signature: Notificacións Mastodon de %{instance}
view: 'Vista:'
applications:
created: Creouse con éxito este aplicativo

View file

@ -229,7 +229,6 @@ he:
title: ניהול
application_mailer:
settings: 'שינוי הגדרות דוא"ל: %{link}'
signature: התראות מסטודון מקהילת %{instance}
view: 'תצוגה:'
applications:
invalid_url: כתובת הקישורית אינה חוקית

View file

@ -26,7 +26,6 @@ hr:
unfollow: Prestani slijediti
application_mailer:
settings: 'Promijeni e-mail postavke: %{link}'
signature: Mastodon notifikacije sa %{instance}
view: 'Vidi:'
applications:
invalid_url: Uneseni link nije valjan

View file

@ -12,8 +12,6 @@ hu:
people_who_follow: "%{name} követői"
posts: Bejegyzések
unfollow: Követés abbahagyása
application_mailer:
signature: "%{instance} Mastodon értesítései"
auth:
change_password: Jelszó változtatása
didnt_get_confirmation: Nem kaptad meg a megerősítési lépéseket?

View file

@ -151,7 +151,6 @@ id:
title: Administrasi
application_mailer:
settings: 'Ubah pilihan email: %{link}'
signature: Notifikasi Mastodon dari %{instance}
view: 'Tampilan:'
applications:
invalid_url: URL tidak sesuai

View file

@ -149,7 +149,6 @@ io:
title: Administration
application_mailer:
settings: 'Chanjar la retpost-mesajala preferi: %{link}'
signature: Savigi di Mastodon de %{instance}
view: 'Vidar:'
applications:
invalid_url: La URL donita ne esas valida

View file

@ -26,7 +26,6 @@ it:
unfollow: Non seguire più
application_mailer:
settings: 'Cambia le impostazioni per le e-mail: %{link}'
signature: Notifiche Mastodon da %{instance}
view: 'Guarda:'
applications:
invalid_url: L'URL fornito non è valido

View file

@ -341,7 +341,6 @@ ja:
notification_preferences: メール設定の変更
salutation: "%{name} さん"
settings: 'メール設定の変更: %{link}'
signature: Mastodon %{instance} インスタンスからの通知
view: 'リンク:'
view_profile: プロフィールを表示
view_status: トゥートを表示

View file

@ -340,7 +340,6 @@ ko:
application_mailer:
notification_preferences: 메일 설정 변경
settings: '메일 설정을 변경: %{link}'
signature: Mastodon %{instance} 인스턴스로에서 알림
view: 'View:'
view_profile: 프로필 보기
view_status: 게시물 보기

View file

@ -340,7 +340,6 @@ nl:
application_mailer:
salutation: "%{name},"
settings: 'E-mailvoorkeuren wijzigen: %{link}'
signature: Mastodon-meldingen van %{instance}
view: 'Bekijk:'
applications:
created: Aanmaken toepassing geslaagd

View file

@ -163,7 +163,6 @@
title: Administrasjon
application_mailer:
settings: 'Endre foretrukne e-postinnstillinger: %{link}'
signature: Mastodon-notiser fra %{instance}
view: 'Se:'
applications:
invalid_url: Den oppgitte URLen er ugyldig

View file

@ -340,7 +340,6 @@ oc:
application_mailer:
salutation: "%{name},"
settings: 'Cambiar las preferéncias de corrièl: %{link}'
signature: Notificacion de Mastodon sus %{instance}
view: 'Veire:'
applications:
created: Aplicacion ben creada

View file

@ -342,7 +342,6 @@ pl:
notification_preferences: Zmień ustawienia e-maili
salutation: "%{name},"
settings: 'Zmień ustawienia powiadamiania: %{link}'
signature: Powiadomienie Mastodona z instancji %{instance}
view: 'Zobacz:'
view_status: Wyświetl wpis
applications:
@ -740,6 +739,25 @@ pl:
recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. <strong>Trzymaj je w bezpiecznym miejscu</strong>. Na przykład, wydrukuj je i przechowuj z ważnymi dokumentami.
setup: Skonfiguruj
wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny?
user_mailer:
welcome:
edit_profile_action: Skonfiguruj profil
edit_profile_step: Możesz dostować profil wysyłając awatar, obraz nagłówka, zmieniając wyświetlaną nazwę i wiele więcej. Jeżeli chcesz, możesz zablokować konto, aby kontrolować, kto może Cię śledzić.
explanation: Kilka wskazówek, które pomogą Ci rozpocząć
final_action: Zacznij pisać
final_step: 'Zacznij tworzyć! Nawet jeżeli nikt Cię nie śledzi, Twoje publiczne wiadomości będą widziane przez innych, na przykład na lokalnej osi czasu i w hashtagach. Możesz też utworzyć wpis wprowadzający używając hashtagu #introductions.'
full_handle: Twój pełny adres
full_handle_hint: Ten adres możesz podać znajomym, aby mogli skontaktować się z Tobą lub zacząć śledzić z innej instancji.
review_preferences_action: Zmień ustawienia
review_preferences_step: Upewnij się, że zmieniłeś ustawienia, takie jak maile, które chciałbyś otrzymywać lub domyślne opcje prywatności. Jeżeli nie masz choroby lokomocyjnej, możesz włączyć automatyczne odtwarzanie animacji GIF.
subject: Witaj w Mastodonie
tip_bridge_html: Jeżeli przybywasz z Twittera, możesz znaleźć znajomych na Mastodonie używając <a href="%{bridge_url}">aplikacji mostku</a>. Działa to tylko, jeżeli oni również z niej korzystali!
tip_federated_timeline: Oś czasu federacji przedstawia całą sieć Mastodona. Wyświetla tylko wpisy osób, które śledzą użytkownicy Twojej instancji, więc nie jest kompletna.
tip_following: Domyślnie śledzisz administratora/ów swojej instancji. Aby znaleźć więcej ciekawych ludzi, zajrzyj na lokalną i federalną oś czasu.
tip_local_timeline: Lokalna oś czasu przedstawia osoby z %{instance}. To Twoi najbliżsi sąsiedzi!
tip_mobile_webapp: Jeżeli Twoja przeglądarka pozwala na dodanie Mastodona na ekran główny, będziesz otrzymywać natychmiastowe powiadomienia. Działa to prawie jak natywna aplikacja!
tips: Wskazówki
title: Witaj na pokładzie, %{name}!
users:
invalid_email: Adres e-mail jest niepoprawny
invalid_otp_token: Kod uwierzytelniający jest niepoprawny

View file

@ -340,7 +340,6 @@ pt-BR:
application_mailer:
salutation: "%{name},"
settings: 'Mudar e-mail de preferência: %{link}'
signature: Notificações do Mastodon de %{instance}
view: 'Visualizar:'
applications:
created: Aplicação criada com sucesso

View file

@ -328,7 +328,6 @@ pt:
application_mailer:
salutation: "%{name},"
settings: 'Alterar preferências de email: %{link}'
signature: notificações Mastodon do %{instance}
view: 'Ver:'
applications:
created: Aplicação criada com sucesso

View file

@ -342,7 +342,6 @@ ru:
notification_preferences: Изменить настройки e-mail
salutation: "%{name},"
settings: 'Изменить настройки e-mail: %{link}'
signature: Уведомления Mastodon от %{instance}
view: 'Просмотр:'
view_status: Просмотреть статус
applications:

View file

@ -336,7 +336,6 @@ sr-Latn:
application_mailer:
salutation: "%{name},"
settings: 'Promeni podešavanja e-pošte: %{link}'
signature: Mastodont obaveštenje sa instance %{instance}
view: 'Pogledaj:'
applications:
created: Aplikacija uspešno napravljena

View file

@ -336,7 +336,6 @@ sr:
application_mailer:
salutation: "%{name},"
settings: 'Промени подешавања е-поште: %{link}'
signature: Мастодонт обавештење са инстанце %{instance}
view: 'Погледај:'
applications:
created: Апликација успешно направљена

View file

@ -272,7 +272,6 @@ sv:
application_mailer:
salutation: "%{name},"
settings: 'Change e-mail preferences: %{link}'
signature: Mastodon meddelande från %{instance}
view: 'Granska:'
applications:
created: Ansökan är framgångsrikt skapad

View file

@ -153,7 +153,6 @@ th:
title: แอดมิน
application_mailer:
settings: 'เปลี่ยนอีเมล์ preferences: %{link}'
signature: ฟอร์มการแจ้งเตือนแมสโทดอน %{instance}
view: 'วิว:'
applications:
invalid_url: URL ที่ระบุไม่ถูกตั้ง

View file

@ -152,7 +152,6 @@ tr:
title: Yönetim
application_mailer:
settings: 'E-mail tercihlerini değiştir: %{link}'
signature: "%{instance} sunucusundan Mastodon bildirimleri"
view: 'Görüntüle:'
applications:
invalid_url: Verilen URL geçerli değil

View file

@ -143,7 +143,6 @@ uk:
title: Адміністрування
application_mailer:
settings: 'Змінити налаштування email: %{link}'
signature: Сповіщення Mastodon від %{instance}
view: 'Перегляд:'
applications:
invalid_url: Введена URL неправильна

View file

@ -339,7 +339,6 @@ zh-CN:
notification_preferences: 更改电子邮件首选项
salutation: "%{name}"
settings: 使用此链接更改你的电子邮件首选项:%{link}
signature: 这是一封来自 %{instance} 的 Mastodon 电子邮件通知。
view: 点此链接查看详情:
view_profile: 查看个人资料页
view_status: 查看嘟文

View file

@ -152,7 +152,6 @@ zh-HK:
title: 管理
application_mailer:
settings: 修改電郵設定︰%{link}
signature: 來自 %{instance} 的 Mastodon 通知
view: 進入瀏覽︰
applications:
invalid_url: 所提供的網址不正確

View file

@ -123,7 +123,6 @@ zh-TW:
title: 管理介面
application_mailer:
settings: 修改信箱設定︰ %{link}
signature: 來自 %{instance} 的 Mastodon 通知
view: 進入瀏覽︰
applications:
invalid_url: 網址不正確

View file

@ -341,6 +341,15 @@ namespace :mastodon do
LinkCrawlWorker.push_bulk status_ids
end
desc 'Remove all home feed regeneration markers'
task remove_regeneration_markers: :environment do
keys = Redis.current.keys('account:*:regeneration')
Redis.current.pipelined do
keys.each { |key| Redis.current.del(key) }
end
end
desc 'Check every known remote account and delete those that no longer exist in origin'
task purge_removed_accounts: :environment do
prepare_for_options!

View file

@ -69,6 +69,12 @@ describe ApplicationController, type: :controller do
expect(RegenerationWorker).to have_received(:perform_async)
end
it 'sets the regeneration marker to expire' do
allow(RegenerationWorker).to receive(:perform_async)
get :show
expect(Redis.current.ttl("account:#{user.account_id}:regeneration")).to be >= 0
end
it 'regenerates feed when sign in is older than two weeks' do
get :show

View file

@ -29,4 +29,9 @@ class UserMailerPreview < ActionMailer::Preview
def reset_password_instructions
UserMailer.reset_password_instructions(User.first, 'spec')
end
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/welcome
def welcome
UserMailer.welcome(User.first)
end
end