mirror of
https://framagit.org/tykayn/mastodon.git
synced 2023-08-25 08:33:12 +02:00
Merge branch 'master' into master
This commit is contained in:
commit
bf7cefa516
2
.buildpacks
Normal file
2
.buildpacks
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
https://github.com/Scalingo/nodejs-buildpack
|
||||||
|
https://github.com/Scalingo/ruby-buildpack
|
@ -22,6 +22,8 @@ OTP_SECRET=
|
|||||||
# SINGLE_USER_MODE=true
|
# SINGLE_USER_MODE=true
|
||||||
# Prevent registrations with following e-mail domains
|
# Prevent registrations with following e-mail domains
|
||||||
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
|
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
|
||||||
|
# Only allow registrations with the following e-mail domains
|
||||||
|
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
|
||||||
|
|
||||||
# E-mail configuration
|
# E-mail configuration
|
||||||
SMTP_SERVER=smtp.mailgun.org
|
SMTP_SERVER=smtp.mailgun.org
|
||||||
|
2
.slugignore
Normal file
2
.slugignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
.cache/
|
8
Gemfile
8
Gemfile
@ -8,8 +8,6 @@ gem 'sass-rails', '~> 5.0'
|
|||||||
gem 'uglifier', '>= 1.3.0'
|
gem 'uglifier', '>= 1.3.0'
|
||||||
gem 'coffee-rails', '~> 4.1.0'
|
gem 'coffee-rails', '~> 4.1.0'
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
gem 'jbuilder', '~> 2.0'
|
|
||||||
gem 'sdoc', '~> 0.4.0', group: :doc
|
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
|
|
||||||
gem 'hamlit-rails'
|
gem 'hamlit-rails'
|
||||||
@ -38,7 +36,7 @@ gem 'rqrcode'
|
|||||||
gem 'twitter-text'
|
gem 'twitter-text'
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
gem 'hiredis'
|
gem 'hiredis'
|
||||||
gem 'redis', '~>3.2'
|
gem 'redis', '~>3.2', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'fast_blank'
|
gem 'fast_blank'
|
||||||
gem 'htmlentities'
|
gem 'htmlentities'
|
||||||
gem 'simple_form'
|
gem 'simple_form'
|
||||||
@ -46,6 +44,7 @@ gem 'will_paginate'
|
|||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
gem 'rack-cors', require: 'rack/cors'
|
gem 'rack-cors', require: 'rack/cors'
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
|
gem 'sidekiq-unique-jobs'
|
||||||
gem 'rails-settings-cached'
|
gem 'rails-settings-cached'
|
||||||
gem 'simple-navigation'
|
gem 'simple-navigation'
|
||||||
gem 'statsd-instrument'
|
gem 'statsd-instrument'
|
||||||
@ -66,9 +65,10 @@ group :development, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
gem 'faker'
|
||||||
|
gem 'rspec-sidekiq'
|
||||||
gem 'simplecov', require: false
|
gem 'simplecov', require: false
|
||||||
gem 'webmock'
|
gem 'webmock'
|
||||||
gem 'rspec-sidekiq'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
18
Gemfile.lock
18
Gemfile.lock
@ -149,6 +149,8 @@ GEM
|
|||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
fabrication (2.15.2)
|
fabrication (2.15.2)
|
||||||
|
faker (1.6.6)
|
||||||
|
i18n (~> 0.5)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
font-awesome-rails (4.6.3.1)
|
font-awesome-rails (4.6.3.1)
|
||||||
railties (>= 3.2, < 5.1)
|
railties (>= 3.2, < 5.1)
|
||||||
@ -196,9 +198,6 @@ GEM
|
|||||||
parser (>= 2.2.3.0)
|
parser (>= 2.2.3.0)
|
||||||
term-ansicolor (>= 1.3.2)
|
term-ansicolor (>= 1.3.2)
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
jbuilder (2.6.0)
|
|
||||||
activesupport (>= 3.0.0, < 5.1)
|
|
||||||
multi_json (~> 1.2)
|
|
||||||
jmespath (1.3.1)
|
jmespath (1.3.1)
|
||||||
jquery-rails (4.1.1)
|
jquery-rails (4.1.1)
|
||||||
rails-dom-testing (>= 1, < 3)
|
rails-dom-testing (>= 1, < 3)
|
||||||
@ -229,7 +228,6 @@ GEM
|
|||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.1.0)
|
||||||
minitest (5.10.1)
|
minitest (5.10.1)
|
||||||
multi_json (1.12.1)
|
|
||||||
net-scp (1.2.1)
|
net-scp (1.2.1)
|
||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (4.0.1)
|
net-ssh (4.0.1)
|
||||||
@ -308,8 +306,6 @@ GEM
|
|||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.1.0)
|
rainbow (2.1.0)
|
||||||
rake (12.0.0)
|
rake (12.0.0)
|
||||||
rdoc (4.2.2)
|
|
||||||
json (~> 1.4)
|
|
||||||
react-rails (1.10.0)
|
react-rails (1.10.0)
|
||||||
babel-transpiler (>= 0.7.0)
|
babel-transpiler (>= 0.7.0)
|
||||||
coffee-script-source (~> 1.8)
|
coffee-script-source (~> 1.8)
|
||||||
@ -379,14 +375,14 @@ GEM
|
|||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
sprockets-rails (>= 2.0, < 4.0)
|
sprockets-rails (>= 2.0, < 4.0)
|
||||||
tilt (>= 1.1, < 3)
|
tilt (>= 1.1, < 3)
|
||||||
sdoc (0.4.1)
|
|
||||||
json (~> 1.7, >= 1.7.7)
|
|
||||||
rdoc (~> 4.0)
|
|
||||||
sidekiq (4.2.7)
|
sidekiq (4.2.7)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
connection_pool (~> 2.2, >= 2.2.0)
|
connection_pool (~> 2.2, >= 2.2.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
redis (~> 3.2, >= 3.2.1)
|
redis (~> 3.2, >= 3.2.1)
|
||||||
|
sidekiq-unique-jobs (4.0.18)
|
||||||
|
sidekiq (>= 2.6)
|
||||||
|
thor
|
||||||
simple-navigation (4.0.3)
|
simple-navigation (4.0.3)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
simple_form (3.2.1)
|
simple_form (3.2.1)
|
||||||
@ -467,6 +463,7 @@ DEPENDENCIES
|
|||||||
doorkeeper
|
doorkeeper
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
fabrication
|
fabrication
|
||||||
|
faker
|
||||||
fast_blank
|
fast_blank
|
||||||
font-awesome-rails
|
font-awesome-rails
|
||||||
fuubar
|
fuubar
|
||||||
@ -477,7 +474,6 @@ DEPENDENCIES
|
|||||||
http
|
http
|
||||||
httplog
|
httplog
|
||||||
i18n-tasks (~> 0.9.6)
|
i18n-tasks (~> 0.9.6)
|
||||||
jbuilder (~> 2.0)
|
|
||||||
jquery-rails
|
jquery-rails
|
||||||
letter_opener
|
letter_opener
|
||||||
letter_opener_web
|
letter_opener_web
|
||||||
@ -508,8 +504,8 @@ DEPENDENCIES
|
|||||||
rubocop
|
rubocop
|
||||||
ruby-oembed
|
ruby-oembed
|
||||||
sass-rails (~> 5.0)
|
sass-rails (~> 5.0)
|
||||||
sdoc (~> 0.4.0)
|
|
||||||
sidekiq
|
sidekiq
|
||||||
|
sidekiq-unique-jobs
|
||||||
simple-navigation
|
simple-navigation
|
||||||
simple_form
|
simple_form
|
||||||
simplecov
|
simplecov
|
||||||
|
5
ISSUE_TEMPLATE.md
Normal file
5
ISSUE_TEMPLATE.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[Issue text goes here].
|
||||||
|
|
||||||
|
* * * *
|
||||||
|
|
||||||
|
- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate.
|
@ -117,6 +117,12 @@ Which will re-create the updated containers, leaving databases and data as is. D
|
|||||||
|
|
||||||
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
|
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
|
||||||
|
|
||||||
|
## Deployment on Scalingo
|
||||||
|
|
||||||
|
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
|
||||||
|
|
||||||
|
[You can view a guide for deployment on Scalingo here.](docs/Running-Mastodon/Scalingo-guide.md)
|
||||||
|
|
||||||
## Deployment on Heroku (experimental)
|
## Deployment on Heroku (experimental)
|
||||||
|
|
||||||
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
|
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 59 KiB |
@ -46,6 +46,7 @@ import fr from 'react-intl/locale-data/fr';
|
|||||||
import pt from 'react-intl/locale-data/pt';
|
import pt from 'react-intl/locale-data/pt';
|
||||||
import hu from 'react-intl/locale-data/hu';
|
import hu from 'react-intl/locale-data/hu';
|
||||||
import uk from 'react-intl/locale-data/uk';
|
import uk from 'react-intl/locale-data/uk';
|
||||||
|
import fi from 'react-intl/locale-data/fi';
|
||||||
import eo from 'react-intl/locale-data/eo';
|
import eo from 'react-intl/locale-data/eo';
|
||||||
import getMessagesForLocale from '../locales';
|
import getMessagesForLocale from '../locales';
|
||||||
import { hydrateStore } from '../actions/store';
|
import { hydrateStore } from '../actions/store';
|
||||||
@ -59,7 +60,7 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
|
|||||||
basename: '/web'
|
basename: '/web'
|
||||||
});
|
});
|
||||||
|
|
||||||
addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk, ...eo]);
|
addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk, ...fi, ...eo]);
|
||||||
|
|
||||||
const Mastodon = React.createClass({
|
const Mastodon = React.createClass({
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ const GettingStarted = ({ intl, me }) => {
|
|||||||
|
|
||||||
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
|
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<div className='static-content getting-started'>
|
<div className='static-content getting-started'>
|
||||||
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on github at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
|
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
|
@ -9,7 +9,7 @@ const iconStyle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ClearColumnButton = ({ onClick }) => (
|
const ClearColumnButton = ({ onClick }) => (
|
||||||
<div className='column-icon' style={iconStyle} onClick={onClick}>
|
<div className='column-icon' tabindex='0' style={iconStyle} onClick={onClick}>
|
||||||
<i className='fa fa-trash' />
|
<i className='fa fa-trash' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,7 @@ const en = {
|
|||||||
"getting_started.heading": "Getting started",
|
"getting_started.heading": "Getting started",
|
||||||
"getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.",
|
"getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.",
|
||||||
"getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.",
|
"getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.",
|
||||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on github at {github}. {apps}.",
|
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
|
||||||
"column.home": "Home",
|
"column.home": "Home",
|
||||||
"column.community": "Local timeline",
|
"column.community": "Local timeline",
|
||||||
"column.public": "Federated timeline",
|
"column.public": "Federated timeline",
|
||||||
|
68
app/assets/javascripts/components/locales/fi.jsx
Normal file
68
app/assets/javascripts/components/locales/fi.jsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const fi = {
|
||||||
|
"column_back_button.label": "Takaisin",
|
||||||
|
"lightbox.close": "Sulje",
|
||||||
|
"loading_indicator.label": "Ladataan...",
|
||||||
|
"status.mention": "Mainitse @{name}",
|
||||||
|
"status.delete": "Poista",
|
||||||
|
"status.reply": "Vastaa",
|
||||||
|
"status.reblog": "Buustaa",
|
||||||
|
"status.favourite": "Tykkää",
|
||||||
|
"status.reblogged_by": "{name} buustasi",
|
||||||
|
"status.sensitive_warning": "Arkaluontoista sisältöä",
|
||||||
|
"status.sensitive_toggle": "Klikkaa nähdäksesi",
|
||||||
|
"video_player.toggle_sound": "Äänet päälle/pois",
|
||||||
|
"account.mention": "Mainitse @{name}",
|
||||||
|
"account.edit_profile": "Muokkaa",
|
||||||
|
"account.unblock": "Salli @{name}",
|
||||||
|
"account.unfollow": "Lopeta seuraaminen",
|
||||||
|
"account.block": "Estä @{name}",
|
||||||
|
"account.follow": "Seuraa",
|
||||||
|
"account.posts": "Postit",
|
||||||
|
"account.follows": "Seuraa",
|
||||||
|
"account.followers": "Seuraajia",
|
||||||
|
"account.follows_you": "Seuraa sinua",
|
||||||
|
"account.requested": "Odottaa hyväksyntää",
|
||||||
|
"getting_started.heading": "Aloitus",
|
||||||
|
"getting_started.about_addressing": "Voit seurata ihmisiä jos tiedät heidän käyttäjänimensä ja domainin missä he ovat syöttämällä e-mail-esque osoitteen Etsi kenttään.",
|
||||||
|
"getting_started.about_shortcuts": "Jos etsimäsi henkilö on samassa domainissa kuin sinä, pelkkä käyttäjänimi kelpaa. Sama pätee kun mainitset ihmisiä statuksessasi",
|
||||||
|
"getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.",
|
||||||
|
"column.home": "Koti",
|
||||||
|
"column.community": "Paikallinen aikajana",
|
||||||
|
"column.public": "Yleinen aikajana",
|
||||||
|
"column.notifications": "Ilmoitukset",
|
||||||
|
"tabs_bar.compose": "Luo",
|
||||||
|
"tabs_bar.home": "Koti",
|
||||||
|
"tabs_bar.mentions": "Maininnat",
|
||||||
|
"tabs_bar.public": "Yleinen aikajana",
|
||||||
|
"tabs_bar.notifications": "Ilmoitukset",
|
||||||
|
"compose_form.placeholder": "Mitä sinulla on mielessä?",
|
||||||
|
"compose_form.publish": "Toot",
|
||||||
|
"compose_form.sensitive": "Merkitse media herkäksi",
|
||||||
|
"compose_form.spoiler": "Piiloita teksti varoituksen taakse",
|
||||||
|
"compose_form.private": "Merkitse yksityiseksi",
|
||||||
|
"compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.",
|
||||||
|
"compose_form.unlisted": "Älä näytä yleisillä aikajanoilla",
|
||||||
|
"navigation_bar.edit_profile": "Muokkaa profiilia",
|
||||||
|
"navigation_bar.preferences": "Ominaisuudet",
|
||||||
|
"navigation_bar.community_timeline": "Paikallinen aikajana",
|
||||||
|
"navigation_bar.public_timeline": "Yleinen aikajana",
|
||||||
|
"navigation_bar.logout": "Kirjaudu ulos",
|
||||||
|
"reply_indicator.cancel": "Peruuta",
|
||||||
|
"search.placeholder": "Hae",
|
||||||
|
"search.account": "Tili",
|
||||||
|
"search.hashtag": "Hashtag",
|
||||||
|
"upload_button.label": "Lisää mediaa",
|
||||||
|
"upload_form.undo": "Peru",
|
||||||
|
"notification.follow": "{name} seurasi sinua",
|
||||||
|
"notification.favourite": "{name} tykkäsi statuksestasi",
|
||||||
|
"notification.reblog": "{name} buustasi statustasi",
|
||||||
|
"notification.mention": "{name} mainitsi sinut",
|
||||||
|
"notifications.column_settings.alert": "Työpöytä ilmoitukset",
|
||||||
|
"notifications.column_settings.show": "Näytä sarakkeessa",
|
||||||
|
"notifications.column_settings.follow": "Uusia seuraajia:",
|
||||||
|
"notifications.column_settings.favourite": "Tykkäyksiä:",
|
||||||
|
"notifications.column_settings.mention": "Mainintoja:",
|
||||||
|
"notifications.column_settings.reblog": "Buusteja:",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default fi;
|
@ -5,6 +5,7 @@ import hu from './hu';
|
|||||||
import fr from './fr';
|
import fr from './fr';
|
||||||
import pt from './pt';
|
import pt from './pt';
|
||||||
import uk from './uk';
|
import uk from './uk';
|
||||||
|
import fi from './fi';
|
||||||
import eo from './eo';
|
import eo from './eo';
|
||||||
|
|
||||||
const locales = {
|
const locales = {
|
||||||
@ -15,6 +16,7 @@ const locales = {
|
|||||||
fr,
|
fr,
|
||||||
pt,
|
pt,
|
||||||
uk,
|
uk,
|
||||||
|
fi,
|
||||||
eo
|
eo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple_form {
|
.simple_form, .closed-registrations-message {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
background: rgba(darken($color1, 7%), 0.5);
|
background: rgba(darken($color1, 7%), 0.5);
|
||||||
@ -340,3 +340,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.closed-registrations-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
text-shadow: 0 0 2px $color8;
|
||||||
|
|
||||||
small {
|
small {
|
||||||
display: block;
|
display: block;
|
||||||
@ -128,6 +129,7 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
text-shadow: 0 0 2px $color8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-number {
|
.counter-number {
|
||||||
@ -385,5 +387,6 @@
|
|||||||
.account__header__content {
|
.account__header__content {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $color1;
|
color: $color1;
|
||||||
|
text-shadow: 0 0 2px $color8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ class AboutController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@description = Setting.site_description
|
@description = Setting.site_description
|
||||||
|
@open_registrations = Setting.open_registrations
|
||||||
|
@closed_registrations_message = Setting.closed_registrations_message
|
||||||
|
|
||||||
@user = User.new
|
@user = User.new
|
||||||
@user.build_account
|
@user.build_account
|
||||||
|
@ -9,6 +9,24 @@ class Admin::DomainBlocksController < ApplicationController
|
|||||||
@blocks = DomainBlock.paginate(page: params[:page], per_page: 40)
|
@blocks = DomainBlock.paginate(page: params[:page], per_page: 40)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@domain_block = DomainBlock.new
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@domain_block = DomainBlock.new(resource_params)
|
||||||
|
|
||||||
|
if @domain_block.save
|
||||||
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
|
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
|
||||||
|
else
|
||||||
|
render action: :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:domain_block).permit(:domain, :severity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -16,19 +16,19 @@ class Admin::ReportsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def resolve
|
def resolve
|
||||||
@report.update(action_taken: true)
|
@report.update(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
def suspend
|
def suspend
|
||||||
Admin::SuspensionWorker.perform_async(@report.target_account.id)
|
Admin::SuspensionWorker.perform_async(@report.target_account.id)
|
||||||
@report.update(action_taken: true)
|
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
def silence
|
def silence
|
||||||
@report.target_account.update(silenced: true)
|
@report.target_account.update(silenced: true)
|
||||||
@report.update(action_taken: true)
|
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -11,9 +11,13 @@ class Admin::SettingsController < ApplicationController
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
|
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
|
||||||
|
value = settings_params[:value]
|
||||||
|
|
||||||
if @setting.value != params[:setting][:value]
|
# Special cases
|
||||||
@setting.value = params[:setting][:value]
|
value = value == 'true' if @setting.var == 'open_registrations'
|
||||||
|
|
||||||
|
if @setting.value != value
|
||||||
|
@setting.value = value
|
||||||
@setting.save
|
@setting.save
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -22,4 +26,10 @@ class Admin::SettingsController < ApplicationController
|
|||||||
format.json { respond_with_bip(@setting) }
|
format.json { respond_with_bip(@setting) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def settings_params
|
||||||
|
params.require(:setting).permit(:value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,12 @@ class Api::V1::AppsController < ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@app = Doorkeeper::Application.create!(name: params[:client_name], redirect_uri: params[:redirect_uris], scopes: (params[:scopes] || Doorkeeper.configuration.default_scopes), website: params[:website])
|
@app = Doorkeeper::Application.create!(name: app_params[:client_name], redirect_uri: app_params[:redirect_uris], scopes: (app_params[:scopes] || Doorkeeper.configuration.default_scopes), website: app_params[:website])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def app_params
|
||||||
|
params.permit(:client_name, :redirect_uris, :scopes, :website)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -7,7 +7,7 @@ class Api::V1::FollowsController < ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
raise ActiveRecord::RecordNotFound if params[:uri].blank?
|
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
|
||||||
|
|
||||||
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
||||||
render action: :show
|
render action: :show
|
||||||
@ -16,6 +16,10 @@ class Api::V1::FollowsController < ApiController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def target_uri
|
def target_uri
|
||||||
params[:uri].strip.gsub(/\A@/, '')
|
follow_params[:uri].strip.gsub(/\A@/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_params
|
||||||
|
params.permit(:uri)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,10 +10,16 @@ class Api::V1::MediaController < ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@media = MediaAttachment.create!(account: current_user.account, file: params[:file])
|
@media = MediaAttachment.create!(account: current_user.account, file: media_params[:file])
|
||||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||||
render json: { error: 'File type of uploaded media could not be verified' }, status: 422
|
render json: { error: 'File type of uploaded media could not be verified' }, status: 422
|
||||||
rescue Paperclip::Error
|
rescue Paperclip::Error
|
||||||
render json: { error: 'Error processing thumbnail for uploaded media' }, status: 500
|
render json: { error: 'Error processing thumbnail for uploaded media' }, status: 500
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def media_params
|
||||||
|
params.permit(:file)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,13 +12,19 @@ class Api::V1::ReportsController < ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
status_ids = params[:status_ids].is_a?(Enumerable) ? params[:status_ids] : [params[:status_ids]]
|
status_ids = report_params[:status_ids].is_a?(Enumerable) ? report_params[:status_ids] : [report_params[:status_ids]]
|
||||||
|
|
||||||
@report = Report.create!(account: current_account,
|
@report = Report.create!(account: current_account,
|
||||||
target_account: Account.find(params[:account_id]),
|
target_account: Account.find(report_params[:account_id]),
|
||||||
status_ids: Status.find(status_ids).pluck(:id),
|
status_ids: Status.find(status_ids).pluck(:id),
|
||||||
comment: params[:comment])
|
comment: report_params[:comment])
|
||||||
|
|
||||||
render :show
|
render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def report_params
|
||||||
|
params.permit(:account_id, :comment, status_ids: [])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -62,10 +62,10 @@ class Api::V1::StatusesController < ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
|
@status = PostStatusService.new.call(current_user.account, status_params[:status], status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), media_ids: status_params[:media_ids],
|
||||||
sensitive: params[:sensitive],
|
sensitive: status_params[:sensitive],
|
||||||
spoiler_text: params[:spoiler_text],
|
spoiler_text: status_params[:spoiler_text],
|
||||||
visibility: params[:visibility],
|
visibility: status_params[:visibility],
|
||||||
application: doorkeeper_token.application)
|
application: doorkeeper_token.application)
|
||||||
render action: :show
|
render action: :show
|
||||||
end
|
end
|
||||||
@ -111,4 +111,8 @@ class Api::V1::StatusesController < ApiController
|
|||||||
@status = Status.find(params[:id])
|
@status = Status.find(params[:id])
|
||||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def status_params
|
||||||
|
params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: [])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -39,7 +39,14 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_user_activity
|
def set_user_activity
|
||||||
current_user.touch(:current_sign_in_at) if !current_user.nil? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < 24.hours.ago)
|
return unless !current_user.nil? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < 24.hours.ago)
|
||||||
|
|
||||||
|
# Mark user as signed-in today
|
||||||
|
current_user.update_tracked_fields(request)
|
||||||
|
|
||||||
|
# If the sign in is after a two week break, we need to regenerate their feed
|
||||||
|
RegenerationWorker.perform_async(current_user.account_id) if current_user.last_sign_in_at < 14.days.ago
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_suspension
|
def check_suspension
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class Auth::RegistrationsController < Devise::RegistrationsController
|
class Auth::RegistrationsController < Devise::RegistrationsController
|
||||||
layout :determine_layout
|
layout :determine_layout
|
||||||
|
|
||||||
before_action :check_single_user_mode
|
before_action :check_enabled_registrations, only: [:new, :create]
|
||||||
before_action :configure_sign_up_params, only: [:create]
|
before_action :configure_sign_up_params, only: [:create]
|
||||||
|
|
||||||
protected
|
protected
|
||||||
@ -27,8 +27,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
new_user_session_path
|
new_user_session_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_single_user_mode
|
def check_enabled_registrations
|
||||||
redirect_to root_path if Rails.configuration.x.single_user_mode
|
redirect_to root_path if Rails.configuration.x.single_user_mode || !Setting.open_registrations
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||||
skip_before_action :authenticate_resource_owner!
|
skip_before_action :authenticate_resource_owner!
|
||||||
|
|
||||||
|
before_action :set_locale
|
||||||
before_action :store_current_location
|
before_action :store_current_location
|
||||||
before_action :authenticate_resource_owner!
|
before_action :authenticate_resource_owner!
|
||||||
|
|
||||||
@ -11,4 +12,10 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||||||
def store_current_location
|
def store_current_location
|
||||||
store_location_for(:user, request.url)
|
store_location_for(:user, request.url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_locale
|
||||||
|
I18n.locale = current_user.try(:locale) || I18n.default_locale
|
||||||
|
rescue I18n::InvalidLocale
|
||||||
|
I18n.locale = I18n.default_locale
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -8,6 +8,7 @@ class RemoteFollowController < ApplicationController
|
|||||||
|
|
||||||
def new
|
def new
|
||||||
@remote_follow = RemoteFollow.new
|
@remote_follow = RemoteFollow.new
|
||||||
|
@remote_follow.acct = session[:remote_follow] if session.key?(:remote_follow)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@ -22,6 +23,8 @@ class RemoteFollowController < ApplicationController
|
|||||||
render(:new) && return
|
render(:new) && return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
session[:remote_follow] = @remote_follow.acct
|
||||||
|
|
||||||
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s
|
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s
|
||||||
else
|
else
|
||||||
render :new
|
render :new
|
||||||
|
@ -10,6 +10,7 @@ module SettingsHelper
|
|||||||
hu: 'Magyar',
|
hu: 'Magyar',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
'zh-CN': '简体中文',
|
'zh-CN': '简体中文',
|
||||||
|
fi: 'Suomi',
|
||||||
eo: 'Esperanto',
|
eo: 'Esperanto',
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
@ -2,17 +2,30 @@
|
|||||||
|
|
||||||
class EmailValidator < ActiveModel::EachValidator
|
class EmailValidator < ActiveModel::EachValidator
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
return if Rails.configuration.x.email_domains_blacklist.empty?
|
|
||||||
|
|
||||||
record.errors.add(attribute, I18n.t('users.invalid_email')) if blocked_email?(value)
|
record.errors.add(attribute, I18n.t('users.invalid_email')) if blocked_email?(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def blocked_email?(value)
|
def blocked_email?(value)
|
||||||
|
on_blacklist?(value) || not_on_whitelist?(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_blacklist?(value)
|
||||||
|
return false if Rails.configuration.x.email_domains_blacklist.blank?
|
||||||
|
|
||||||
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
||||||
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
|
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
|
||||||
|
|
||||||
value =~ regexp
|
value =~ regexp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def not_on_whitelist?(value)
|
||||||
|
return false if Rails.configuration.x.email_domains_whitelist.blank?
|
||||||
|
|
||||||
|
domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
|
||||||
|
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
|
||||||
|
|
||||||
|
value !~ regexp
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,4 +4,5 @@ module Mastodon
|
|||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
class NotPermittedError < Error; end
|
class NotPermittedError < Error; end
|
||||||
class ValidationError < Error; end
|
class ValidationError < Error; end
|
||||||
|
class RaceConditionError < Error; end
|
||||||
end
|
end
|
||||||
|
@ -5,17 +5,17 @@ require 'singleton'
|
|||||||
class FeedManager
|
class FeedManager
|
||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
MAX_ITEMS = 800
|
MAX_ITEMS = 400
|
||||||
|
|
||||||
def key(type, id)
|
def key(type, id)
|
||||||
"feed:#{type}:#{id}"
|
"feed:#{type}:#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter?(timeline_type, status, receiver)
|
def filter?(timeline_type, status, receiver_id)
|
||||||
if timeline_type == :home
|
if timeline_type == :home
|
||||||
filter_from_home?(status, receiver)
|
filter_from_home?(status, receiver_id)
|
||||||
elsif timeline_type == :mentions
|
elsif timeline_type == :mentions
|
||||||
filter_from_mentions?(status, receiver)
|
filter_from_mentions?(status, receiver_id)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
@ -34,12 +34,7 @@ class FeedManager
|
|||||||
trim(timeline_type, account.id)
|
trim(timeline_type, account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status))
|
PushUpdateWorker.perform_async(account.id, status.id)
|
||||||
end
|
|
||||||
|
|
||||||
def broadcast(timeline_id, options = {})
|
|
||||||
options[:queued_at] = (Time.now.to_f * 1000.0).to_i
|
|
||||||
ActionCable.server.broadcast("timeline:#{timeline_id}", options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def trim(type, account_id)
|
def trim(type, account_id)
|
||||||
@ -50,42 +45,35 @@ class FeedManager
|
|||||||
|
|
||||||
def merge_into_timeline(from_account, into_account)
|
def merge_into_timeline(from_account, into_account)
|
||||||
timeline_key = key(:home, into_account.id)
|
timeline_key = key(:home, into_account.id)
|
||||||
|
query = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
|
||||||
|
|
||||||
from_account.statuses.limit(MAX_ITEMS).each do |status|
|
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||||
|
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
|
||||||
|
query = query.where('id > ?', oldest_home_score)
|
||||||
|
end
|
||||||
|
|
||||||
|
redis.pipelined do
|
||||||
|
query.each do |status|
|
||||||
next if status.direct_visibility? || filter?(:home, status, into_account)
|
next if status.direct_visibility? || filter?(:home, status, into_account)
|
||||||
redis.zadd(timeline_key, status.id, status.id)
|
redis.zadd(timeline_key, status.id, status.id)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
trim(:home, into_account.id)
|
trim(:home, into_account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmerge_from_timeline(from_account, into_account)
|
def unmerge_from_timeline(from_account, into_account)
|
||||||
timeline_key = key(:home, into_account.id)
|
timeline_key = key(:home, into_account.id)
|
||||||
|
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
|
||||||
|
|
||||||
from_account.statuses.select('id').find_each do |status|
|
from_account.statuses.select('id').where('id > ?', oldest_home_score).find_in_batches do |statuses|
|
||||||
|
redis.pipelined do
|
||||||
|
statuses.each do |status|
|
||||||
redis.zrem(timeline_key, status.id)
|
redis.zrem(timeline_key, status.id)
|
||||||
redis.zremrangebyscore(timeline_key, status.id, status.id)
|
redis.zremrangebyscore(timeline_key, status.id, status.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def inline_render(target_account, template, object)
|
|
||||||
rabl_scope = Class.new do
|
|
||||||
include RoutingHelper
|
|
||||||
|
|
||||||
def initialize(account)
|
|
||||||
@account = account
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_user
|
|
||||||
@account.try(:user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_account
|
|
||||||
@account
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Rabl::Renderer.new(template, object, view_path: 'app/views', format: :json, scope: rabl_scope.new(target_account)).render
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -94,37 +82,39 @@ class FeedManager
|
|||||||
Redis.current
|
Redis.current
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_from_home?(status, receiver)
|
def filter_from_home?(status, receiver_id)
|
||||||
return true if receiver.muting?(status.account)
|
return true if status.reply? && status.in_reply_to_id.nil?
|
||||||
|
|
||||||
should_filter = false
|
check_for_mutes = [status.account_id]
|
||||||
|
check_for_mutes.concat([status.reblog.account_id]) if status.reblog?
|
||||||
|
|
||||||
if status.reply? && status.in_reply_to_id.nil?
|
return true if Mute.where(account_id: receiver_id, target_account_id: check_for_mutes).any?
|
||||||
should_filter = true
|
|
||||||
elsif status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
|
check_for_blocks = status.mentions.map(&:account_id)
|
||||||
should_filter = !receiver.following?(status.in_reply_to_account) # and I'm not following the person it's a reply to
|
check_for_blocks.concat([status.reblog.account_id]) if status.reblog?
|
||||||
should_filter &&= !(receiver.id == status.in_reply_to_account_id) # and it's not a reply to me
|
|
||||||
|
return true if Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any?
|
||||||
|
|
||||||
|
if status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
|
||||||
|
should_filter = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists? # and I'm not following the person it's a reply to
|
||||||
|
should_filter &&= !(receiver_id == status.in_reply_to_account_id) # and it's not a reply to me
|
||||||
should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
|
should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
|
||||||
|
return should_filter
|
||||||
elsif status.reblog? # Filter out a reblog
|
elsif status.reblog? # Filter out a reblog
|
||||||
should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person
|
return Block.where(account_id: status.reblog.account_id, target_account_id: receiver_id).exists? # or if the author of the reblogged status is blocking me
|
||||||
should_filter ||= receiver.muting?(status.reblog.account) # or muting that person
|
|
||||||
should_filter ||= status.reblog.account.blocking?(receiver) # or if the author of the reblogged status is blocking me
|
|
||||||
end
|
end
|
||||||
|
|
||||||
should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # or if it mentions someone I blocked
|
false
|
||||||
|
|
||||||
should_filter
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_from_mentions?(status, receiver)
|
def filter_from_mentions?(status, receiver_id)
|
||||||
should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself
|
check_for_blocks = [status.account_id]
|
||||||
should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked
|
check_for_blocks.concat(status.mentions.pluck(:account_id))
|
||||||
should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked
|
check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil?
|
||||||
should_filter ||= (status.account.silenced? && !receiver.following?(status.account)) # of if the account is silenced and I'm not following them
|
|
||||||
|
|
||||||
if status.reply? && !status.in_reply_to_account_id.nil? # or it's a reply
|
should_filter = receiver_id == status.account_id # Filter if I'm mentioning myself
|
||||||
should_filter ||= receiver.blocking?(status.in_reply_to_account) # to a user I blocked
|
should_filter ||= Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? # or it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked
|
||||||
end
|
should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them
|
||||||
|
|
||||||
should_filter
|
should_filter
|
||||||
end
|
end
|
||||||
|
17
app/lib/inline_rabl_scope.rb
Normal file
17
app/lib/inline_rabl_scope.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class InlineRablScope
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
|
def initialize(account)
|
||||||
|
@account = account
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
@account.try(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_account
|
||||||
|
@account
|
||||||
|
end
|
||||||
|
end
|
13
app/lib/inline_renderer.rb
Normal file
13
app/lib/inline_renderer.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class InlineRenderer
|
||||||
|
def self.render(status, current_account, template)
|
||||||
|
Rabl::Renderer.new(
|
||||||
|
template,
|
||||||
|
status,
|
||||||
|
view_path: 'app/views',
|
||||||
|
format: :json,
|
||||||
|
scope: InlineRablScope.new(current_account)
|
||||||
|
).render
|
||||||
|
end
|
||||||
|
end
|
@ -3,9 +3,8 @@
|
|||||||
class Block < ApplicationRecord
|
class Block < ApplicationRecord
|
||||||
include Paginable
|
include Paginable
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account, required: true
|
||||||
belongs_to :target_account, class_name: 'Account'
|
belongs_to :target_account, class_name: 'Account', required: true
|
||||||
|
|
||||||
validates :account, :target_account, presence: true
|
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
end
|
end
|
||||||
|
@ -10,17 +10,9 @@ class Feed
|
|||||||
max_id = '+inf' if max_id.blank?
|
max_id = '+inf' if max_id.blank?
|
||||||
since_id = '-inf' if since_id.blank?
|
since_id = '-inf' if since_id.blank?
|
||||||
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:last).map(&:to_i)
|
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:last).map(&:to_i)
|
||||||
|
|
||||||
# If we're after most recent items and none are there, we need to precompute the feed
|
|
||||||
if unhydrated.empty? && max_id == '+inf' && since_id == '-inf'
|
|
||||||
RegenerationWorker.perform_async(@account.id, @type)
|
|
||||||
@statuses = Status.send("as_#{@type}_timeline", @account).cache_ids.paginate_by_max_id(limit, nil, nil)
|
|
||||||
else
|
|
||||||
status_map = Status.where(id: unhydrated).cache_ids.map { |s| [s.id, s] }.to_h
|
status_map = Status.where(id: unhydrated).cache_ids.map { |s| [s.id, s] }.to_h
|
||||||
@statuses = unhydrated.map { |id| status_map[id] }.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
@statuses
|
unhydrated.map { |id| status_map[id] }.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -3,11 +3,14 @@
|
|||||||
class Follow < ApplicationRecord
|
class Follow < ApplicationRecord
|
||||||
include Paginable
|
include Paginable
|
||||||
|
|
||||||
belongs_to :account, counter_cache: :following_count
|
belongs_to :account, counter_cache: :following_count, required: true
|
||||||
belongs_to :target_account, class_name: 'Account', counter_cache: :followers_count
|
|
||||||
|
belongs_to :target_account,
|
||||||
|
class_name: 'Account',
|
||||||
|
counter_cache: :followers_count,
|
||||||
|
required: true
|
||||||
|
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validates :account, :target_account, presence: true
|
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
end
|
end
|
||||||
|
@ -3,12 +3,11 @@
|
|||||||
class FollowRequest < ApplicationRecord
|
class FollowRequest < ApplicationRecord
|
||||||
include Paginable
|
include Paginable
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account, required: true
|
||||||
belongs_to :target_account, class_name: 'Account'
|
belongs_to :target_account, class_name: 'Account', required: true
|
||||||
|
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validates :account, :target_account, presence: true
|
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
|
||||||
def authorize!
|
def authorize!
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Mention < ApplicationRecord
|
class Mention < ApplicationRecord
|
||||||
belongs_to :account, inverse_of: :mentions
|
belongs_to :account, inverse_of: :mentions, required: true
|
||||||
belongs_to :status
|
belongs_to :status, required: true
|
||||||
|
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validates :account, :status, presence: true
|
|
||||||
validates :account, uniqueness: { scope: :status }
|
validates :account, uniqueness: { scope: :status }
|
||||||
end
|
end
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
class Report < ApplicationRecord
|
class Report < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :target_account, class_name: 'Account'
|
belongs_to :target_account, class_name: 'Account'
|
||||||
|
belongs_to :action_taken_by_account, class_name: 'Account'
|
||||||
|
|
||||||
scope :unresolved, -> { where(action_taken: false) }
|
scope :unresolved, -> { where(action_taken: false) }
|
||||||
scope :resolved, -> { where(action_taken: true) }
|
scope :resolved, -> { where(action_taken: true) }
|
||||||
|
@ -161,9 +161,9 @@ class Status < ApplicationRecord
|
|||||||
return where.not(visibility: [:private, :direct]) if account.nil?
|
return where.not(visibility: [:private, :direct]) if account.nil?
|
||||||
|
|
||||||
if target_account.blocking?(account) # get rid of blocked peeps
|
if target_account.blocking?(account) # get rid of blocked peeps
|
||||||
where('1 = 0')
|
none
|
||||||
elsif account.id == target_account.id # author can see own stuff
|
elsif account.id == target_account.id # author can see own stuff
|
||||||
where('1 = 1')
|
all
|
||||||
elsif account.following?(target_account) # followers can see followers-only stuff, but also things they are mentioned in
|
elsif account.following?(target_account) # followers can see followers-only stuff, but also things they are mentioned in
|
||||||
joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = ' + account.id.to_s)
|
joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = ' + account.id.to_s)
|
||||||
.where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:direct])
|
.where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:direct])
|
||||||
@ -188,7 +188,7 @@ class Status < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
before_validation do
|
before_validation do
|
||||||
text.strip!
|
text&.strip!
|
||||||
spoiler_text&.strip!
|
spoiler_text&.strip!
|
||||||
|
|
||||||
self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply
|
self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class BlockDomainService < BaseService
|
class BlockDomainService < BaseService
|
||||||
def call(domain, severity)
|
def call(domain_block)
|
||||||
DomainBlock.where(domain: domain).first_or_create!(domain: domain, severity: severity)
|
if domain_block.silence?
|
||||||
|
Account.where(domain: domain_block.domain).update_all(silenced: true)
|
||||||
if severity == :silence
|
|
||||||
Account.where(domain: domain).update_all(silenced: true)
|
|
||||||
else
|
else
|
||||||
Account.where(domain: domain).find_each do |account|
|
Account.where(domain: domain_block.domain).find_each do |account|
|
||||||
account.subscription(api_subscription_url(account.id)).unsubscribe if account.subscribed?
|
account.subscription(api_subscription_url(account.id)).unsubscribe if account.subscribed?
|
||||||
SuspendAccountService.new.call(account)
|
SuspendAccountService.new.call(account)
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,8 @@ class FanOutOnWriteService < BaseService
|
|||||||
# Push a status into home and mentions feeds
|
# Push a status into home and mentions feeds
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
def call(status)
|
def call(status)
|
||||||
|
raise Mastodon::RaceConditionError if status.visibility.nil?
|
||||||
|
|
||||||
deliver_to_self(status) if status.account.local?
|
deliver_to_self(status) if status.account.local?
|
||||||
|
|
||||||
if status.direct_visibility?
|
if status.direct_visibility?
|
||||||
@ -14,6 +16,7 @@ class FanOutOnWriteService < BaseService
|
|||||||
|
|
||||||
return if status.account.silenced? || !status.public_visibility? || status.reblog?
|
return if status.account.silenced? || !status.public_visibility? || status.reblog?
|
||||||
|
|
||||||
|
render_anonymous_payload(status)
|
||||||
deliver_to_hashtags(status)
|
deliver_to_hashtags(status)
|
||||||
|
|
||||||
return if status.reply? && status.in_reply_to_account_id != status.account_id
|
return if status.reply? && status.in_reply_to_account_id != status.account_id
|
||||||
@ -31,9 +34,8 @@ class FanOutOnWriteService < BaseService
|
|||||||
def deliver_to_followers(status)
|
def deliver_to_followers(status)
|
||||||
Rails.logger.debug "Delivering status #{status.id} to followers"
|
Rails.logger.debug "Delivering status #{status.id} to followers"
|
||||||
|
|
||||||
status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).find_each do |follower|
|
status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).find_each do |follower|
|
||||||
next if FeedManager.instance.filter?(:home, status, follower)
|
FeedInsertWorker.perform_async(status.id, follower.id)
|
||||||
FeedManager.instance.push(:home, follower, status)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -42,28 +44,28 @@ class FanOutOnWriteService < BaseService
|
|||||||
|
|
||||||
status.mentions.includes(:account).each do |mention|
|
status.mentions.includes(:account).each do |mention|
|
||||||
mentioned_account = mention.account
|
mentioned_account = mention.account
|
||||||
next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mentioned_account)
|
next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mention.account_id)
|
||||||
FeedManager.instance.push(:home, mentioned_account, status)
|
FeedManager.instance.push(:home, mentioned_account, status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_anonymous_payload(status)
|
||||||
|
@payload = InlineRenderer.render(status, nil, 'api/v1/statuses/show')
|
||||||
|
end
|
||||||
|
|
||||||
def deliver_to_hashtags(status)
|
def deliver_to_hashtags(status)
|
||||||
Rails.logger.debug "Delivering status #{status.id} to hashtags"
|
Rails.logger.debug "Delivering status #{status.id} to hashtags"
|
||||||
|
|
||||||
payload = FeedManager.instance.inline_render(nil, 'api/v1/statuses/show', status)
|
status.tags.pluck(:name).each do |hashtag|
|
||||||
|
Redis.current.publish("hashtag:#{hashtag}", Oj.dump(event: :update, payload: @payload))
|
||||||
status.tags.find_each do |tag|
|
Redis.current.publish("hashtag:#{hashtag}:local", Oj.dump(event: :update, payload: @payload)) if status.account.local?
|
||||||
FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'update', payload: payload)
|
|
||||||
FeedManager.instance.broadcast("hashtag:#{tag.name}:local", event: 'update', payload: payload) if status.account.local?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_public(status)
|
def deliver_to_public(status)
|
||||||
Rails.logger.debug "Delivering status #{status.id} to public timeline"
|
Rails.logger.debug "Delivering status #{status.id} to public timeline"
|
||||||
|
|
||||||
payload = FeedManager.instance.inline_render(nil, 'api/v1/statuses/show', status)
|
Redis.current.publish('public', Oj.dump(event: 'update', payload: @payload))
|
||||||
|
Redis.current.publish('public:local', Oj.dump(event: 'update', payload: @payload)) if status.account.local?
|
||||||
FeedManager.instance.broadcast(:public, event: 'update', payload: payload)
|
|
||||||
FeedManager.instance.broadcast('public:local', event: 'update', payload: payload) if status.account.local?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -17,7 +17,7 @@ class NotifyService < BaseService
|
|||||||
private
|
private
|
||||||
|
|
||||||
def blocked_mention?
|
def blocked_mention?
|
||||||
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
|
FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked_favourite?
|
def blocked_favourite?
|
||||||
@ -50,7 +50,7 @@ class NotifyService < BaseService
|
|||||||
def create_notification
|
def create_notification
|
||||||
@notification.save!
|
@notification.save!
|
||||||
return unless @notification.browserable?
|
return unless @notification.browserable?
|
||||||
FeedManager.instance.broadcast(@recipient.id, event: 'notification', payload: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification))
|
Redis.current.publish(@recipient.id, Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show')))
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_email
|
def send_email
|
||||||
|
@ -5,11 +5,13 @@ class PrecomputeFeedService < BaseService
|
|||||||
# @param [Symbol] type :home or :mentions
|
# @param [Symbol] type :home or :mentions
|
||||||
# @param [Account] account
|
# @param [Account] account
|
||||||
def call(_, account)
|
def call(_, account)
|
||||||
Status.as_home_timeline(account).limit(FeedManager::MAX_ITEMS).each do |status|
|
redis.pipelined do
|
||||||
next if status.direct_visibility? || FeedManager.instance.filter?(:home, status, account)
|
Status.as_home_timeline(account).limit(FeedManager::MAX_ITEMS / 4).each do |status|
|
||||||
|
next if status.direct_visibility? || FeedManager.instance.filter?(:home, status, account.id)
|
||||||
redis.zadd(FeedManager.instance.key(:home, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
|
redis.zadd(FeedManager.instance.key(:home, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -65,17 +65,17 @@ class RemoveStatusService < BaseService
|
|||||||
redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id)
|
redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
FeedManager.instance.broadcast(receiver.id, event: 'delete', payload: status.id)
|
Redis.current.publish(receiver.id, Oj.dump(event: :delete, payload: status.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_hashtags(status)
|
def remove_from_hashtags(status)
|
||||||
status.tags.each do |tag|
|
status.tags.each do |tag|
|
||||||
FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'delete', payload: status.id)
|
Redis.current.publish("hashtag:#{tag.name}", Oj.dump(event: :delete, payload: status.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_public(status)
|
def remove_from_public(status)
|
||||||
FeedManager.instance.broadcast(:public, event: 'delete', payload: status.id)
|
Redis.current.publish('public', Oj.dump(event: :delete, payload: status.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def redis
|
def redis
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
.screenshot-with-signup
|
.screenshot-with-signup
|
||||||
.mascot= image_tag 'fluffy-elephant-friend.png'
|
.mascot= image_tag 'fluffy-elephant-friend.png'
|
||||||
|
|
||||||
|
- if @open_registrations
|
||||||
= simple_form_for(@user, url: user_registration_path) do |f|
|
= simple_form_for(@user, url: user_registration_path) do |f|
|
||||||
= f.simple_fields_for :account do |ff|
|
= f.simple_fields_for :account do |ff|
|
||||||
= ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
|
= ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
|
||||||
@ -39,6 +40,18 @@
|
|||||||
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
|
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
|
||||||
·
|
·
|
||||||
= link_to t('about.about_this'), about_more_path
|
= link_to t('about.about_this'), about_more_path
|
||||||
|
- else
|
||||||
|
.closed-registrations-message
|
||||||
|
- if @closed_registrations_message.blank?
|
||||||
|
%p= t('about.closed_registrations')
|
||||||
|
- else
|
||||||
|
= @closed_registrations_message.html_safe
|
||||||
|
.info
|
||||||
|
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
|
||||||
|
·
|
||||||
|
= link_to t('about.other_instances'), 'https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md'
|
||||||
|
·
|
||||||
|
= link_to t('about.about_this'), about_more_path
|
||||||
|
|
||||||
%h3= t('about.features_headline')
|
%h3= t('about.features_headline')
|
||||||
|
|
||||||
|
@ -14,3 +14,4 @@
|
|||||||
%td= block.severity
|
%td= block.severity
|
||||||
|
|
||||||
= will_paginate @blocks, pagination_options
|
= will_paginate @blocks, pagination_options
|
||||||
|
= link_to 'Add new', new_admin_domain_block_path, class: 'button'
|
||||||
|
18
app/views/admin/domain_blocks/new.html.haml
Normal file
18
app/views/admin/domain_blocks/new.html.haml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
- content_for :page_title do
|
||||||
|
New domain block
|
||||||
|
|
||||||
|
= simple_form_for @domain_block, url: admin_domain_blocks_path do |f|
|
||||||
|
= render 'shared/error_messages', object: @domain_block
|
||||||
|
|
||||||
|
%p.hint The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
||||||
|
|
||||||
|
= f.input :domain, placeholder: 'Domain'
|
||||||
|
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false
|
||||||
|
|
||||||
|
%p.hint
|
||||||
|
%strong Silence
|
||||||
|
will make the account's posts invisible to anyone who isn't following them.
|
||||||
|
%strong Suspend
|
||||||
|
will remove all of the account's content, media, and profile data.
|
||||||
|
.actions
|
||||||
|
= f.button :button, 'Create block', type: :submit
|
@ -8,9 +8,12 @@
|
|||||||
%li= filter_link_to 'Unresolved', action_taken: nil
|
%li= filter_link_to 'Unresolved', action_taken: nil
|
||||||
%li= filter_link_to 'Resolved', action_taken: '1'
|
%li= filter_link_to 'Resolved', action_taken: '1'
|
||||||
|
|
||||||
|
= form_tag do
|
||||||
|
|
||||||
%table.table
|
%table.table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
%th
|
||||||
%th ID
|
%th ID
|
||||||
%th Target
|
%th Target
|
||||||
%th Reported by
|
%th Reported by
|
||||||
@ -19,9 +22,11 @@
|
|||||||
%tbody
|
%tbody
|
||||||
- @reports.each do |report|
|
- @reports.each do |report|
|
||||||
%tr
|
%tr
|
||||||
|
%td= check_box_tag 'select', report.id
|
||||||
%td= "##{report.id}"
|
%td= "##{report.id}"
|
||||||
%td= link_to report.target_account.acct, admin_account_path(report.target_account.id)
|
%td= link_to report.target_account.acct, admin_account_path(report.target_account.id)
|
||||||
%td= link_to report.account.acct, admin_account_path(report.account.id)
|
%td= link_to report.account.acct, admin_account_path(report.account.id)
|
||||||
%td= truncate(report.comment, length: 30, separator: ' ')
|
%td= truncate(report.comment, length: 30, separator: ' ')
|
||||||
%td= table_link_to 'circle', 'View', admin_report_path(report)
|
%td= table_link_to 'circle', 'View', admin_report_path(report)
|
||||||
|
|
||||||
= will_paginate @reports, pagination_options
|
= will_paginate @reports, pagination_options
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
= link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: 'Delete' do
|
= link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: 'Delete' do
|
||||||
= fa_icon 'trash'
|
= fa_icon 'trash'
|
||||||
|
|
||||||
- unless @report.action_taken?
|
- if !@report.action_taken?
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
%div{ style: 'overflow: hidden' }
|
%div{ style: 'overflow: hidden' }
|
||||||
@ -36,3 +36,9 @@
|
|||||||
= link_to 'Suspend account', suspend_admin_report_path(@report), method: :post, class: 'button'
|
= link_to 'Suspend account', suspend_admin_report_path(@report), method: :post, class: 'button'
|
||||||
%div{ style: 'float: left' }
|
%div{ style: 'float: left' }
|
||||||
= link_to 'Mark as resolved', resolve_admin_report_path(@report), method: :post, class: 'button'
|
= link_to 'Mark as resolved', resolve_admin_report_path(@report), method: :post, class: 'button'
|
||||||
|
- elsif !@report.action_taken_by_account.nil?
|
||||||
|
%hr/
|
||||||
|
|
||||||
|
%p
|
||||||
|
%strong Action taken by:
|
||||||
|
= @report.action_taken_by_account.acct
|
||||||
|
@ -38,3 +38,15 @@
|
|||||||
%br/
|
%br/
|
||||||
You can use HTML tags
|
You can use HTML tags
|
||||||
%td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description'])
|
%td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description'])
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
%strong Open registration
|
||||||
|
%td= best_in_place @settings['open_registrations'], :value, as: :checkbox, collection: { false: 'Disabled', true: 'Enabled'}, url: admin_setting_path(@settings['open_registrations'])
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
%strong Closed registration message
|
||||||
|
%br/
|
||||||
|
Displayed on frontpage when registrations are closed
|
||||||
|
%br/
|
||||||
|
You can use HTML tags
|
||||||
|
%td= best_in_place @settings['closed_registrations_message'], :value, as: :textarea, url: admin_setting_path(@settings['closed_registrations_message'])
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class AfterRemoteFollowRequestWorker
|
class AfterRemoteFollowRequestWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: 5
|
sidekiq_options queue: 'pull', retry: 5
|
||||||
|
|
||||||
def perform(follow_request_id)
|
def perform(follow_request_id)
|
||||||
follow_request = FollowRequest.find(follow_request_id)
|
follow_request = FollowRequest.find(follow_request_id)
|
||||||
@ -13,5 +13,7 @@ class AfterRemoteFollowRequestWorker
|
|||||||
|
|
||||||
follow_request.destroy
|
follow_request.destroy
|
||||||
FollowService.new.call(follow_request.account, updated_account.acct)
|
FollowService.new.call(follow_request.account, updated_account.acct)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class AfterRemoteFollowWorker
|
class AfterRemoteFollowWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: 5
|
sidekiq_options queue: 'pull', retry: 5
|
||||||
|
|
||||||
def perform(follow_id)
|
def perform(follow_id)
|
||||||
follow = Follow.find(follow_id)
|
follow = Follow.find(follow_id)
|
||||||
@ -13,5 +13,7 @@ class AfterRemoteFollowWorker
|
|||||||
|
|
||||||
follow.destroy
|
follow.destroy
|
||||||
FollowService.new.call(follow.account, updated_account.acct)
|
FollowService.new.call(follow.account, updated_account.acct)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
5
app/workers/application_worker.rb
Normal file
5
app/workers/application_worker.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class ApplicationWorker
|
||||||
|
def info(message)
|
||||||
|
Rails.logger.info("#{self.class.name} - #{message}")
|
||||||
|
end
|
||||||
|
end
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class DistributionWorker
|
class DistributionWorker < ApplicationWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
def perform(status_id)
|
def perform(status_id)
|
||||||
@ -9,6 +9,6 @@ class DistributionWorker
|
|||||||
FanOutOnWriteService.new.call(status)
|
FanOutOnWriteService.new.call(status)
|
||||||
WarmCacheService.new.call(status)
|
WarmCacheService.new.call(status)
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
true
|
info("Couldn't find the status")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
11
app/workers/domain_block_worker.rb
Normal file
11
app/workers/domain_block_worker.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DomainBlockWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform(domain_block_id)
|
||||||
|
BlockDomainService.new.call(DomainBlock.find(domain_block_id))
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
15
app/workers/feed_insert_worker.rb
Normal file
15
app/workers/feed_insert_worker.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class FeedInsertWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform(status_id, follower_id)
|
||||||
|
status = Status.find(status_id)
|
||||||
|
follower = Account.find(follower_id)
|
||||||
|
|
||||||
|
return if FeedManager.instance.filter?(:home, status, follower.id)
|
||||||
|
FeedManager.instance.push(:home, follower, status)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
@ -5,7 +5,7 @@ require 'csv'
|
|||||||
class ImportWorker
|
class ImportWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: false
|
sidekiq_options queue: 'pull', retry: false
|
||||||
|
|
||||||
def perform(import_id)
|
def perform(import_id)
|
||||||
import = Import.find(import_id)
|
import = Import.find(import_id)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class LinkCrawlWorker
|
class LinkCrawlWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: false
|
sidekiq_options queue: 'pull', retry: false
|
||||||
|
|
||||||
def perform(status_id)
|
def perform(status_id)
|
||||||
FetchLinkCardService.new.call(Status.find(status_id))
|
FetchLinkCardService.new.call(Status.find(status_id))
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
class MergeWorker
|
class MergeWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
def perform(from_account_id, into_account_id)
|
def perform(from_account_id, into_account_id)
|
||||||
FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id))
|
FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id))
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class NotificationWorker
|
class NotificationWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: 5
|
sidekiq_options queue: 'push', retry: 5
|
||||||
|
|
||||||
def perform(xml, source_account_id, target_account_id)
|
def perform(xml, source_account_id, target_account_id)
|
||||||
SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id))
|
SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id))
|
||||||
|
@ -22,6 +22,7 @@ class Pubsubhubbub::DeliveryWorker
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
.post(subscription.callback_url, body: payload)
|
.post(subscription.callback_url, body: payload)
|
||||||
|
|
||||||
|
return subscription.destroy! if response.code > 299 && response.code < 500 && response.code != 429 # HTTP 4xx means error is not temporary, except for 429 (throttling)
|
||||||
raise "Delivery failed for #{subscription.callback_url}: HTTP #{response.code}" unless response.code > 199 && response.code < 300
|
raise "Delivery failed for #{subscription.callback_url}: HTTP #{response.code}" unless response.code > 199 && response.code < 300
|
||||||
|
|
||||||
subscription.touch(:last_successful_delivery_at)
|
subscription.touch(:last_successful_delivery_at)
|
||||||
|
15
app/workers/push_update_worker.rb
Normal file
15
app/workers/push_update_worker.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PushUpdateWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform(account_id, status_id)
|
||||||
|
account = Account.find(account_id)
|
||||||
|
status = Status.find(status_id)
|
||||||
|
message = InlineRenderer.render(status, account, 'api/v1/statuses/show')
|
||||||
|
|
||||||
|
Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
@ -3,7 +3,9 @@
|
|||||||
class RegenerationWorker
|
class RegenerationWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
def perform(account_id, timeline_type)
|
sidekiq_options queue: 'pull', backtrace: true, unique: :until_executed
|
||||||
PrecomputeFeedService.new.call(timeline_type, Account.find(account_id))
|
|
||||||
|
def perform(account_id, _ = :home)
|
||||||
|
PrecomputeFeedService.new.call(:home, Account.find(account_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class ThreadResolveWorker
|
class ThreadResolveWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
sidekiq_options retry: false
|
sidekiq_options queue: 'pull', retry: false
|
||||||
|
|
||||||
def perform(child_status_id, parent_url)
|
def perform(child_status_id, parent_url)
|
||||||
child_status = Status.find(child_status_id)
|
child_status = Status.find(child_status_id)
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
class UnmergeWorker
|
class UnmergeWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
def perform(from_account_id, into_account_id)
|
def perform(from_account_id, into_account_id)
|
||||||
FeedManager.instance.unmerge_from_timeline(Account.find(from_account_id), Account.find(into_account_id))
|
FeedManager.instance.unmerge_from_timeline(Account.find(from_account_id), Account.find(into_account_id))
|
||||||
end
|
end
|
||||||
|
@ -24,7 +24,7 @@ module Mastodon
|
|||||||
|
|
||||||
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
||||||
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
||||||
config.i18n.available_locales = [:en, :de, :es, :pt, :fr, :hu, :uk, 'zh-CN', :eo]
|
config.i18n.available_locales = [:en, :de, :es, :pt, :fr, :hu, :uk, 'zh-CN', :fi, :eo]
|
||||||
config.i18n.default_locale = :en
|
config.i18n.default_locale = :en
|
||||||
|
|
||||||
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
||||||
|
@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' }
|
config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' }
|
||||||
|
config.x.email_domains_whitelist = ENV.fetch('EMAIL_DOMAIN_WHITELIST') { '' }
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
Rabl.configure do |config|
|
Rabl.configure do |config|
|
||||||
|
config.json_engine = Oj
|
||||||
config.cache_all_output = false
|
config.cache_all_output = false
|
||||||
config.cache_sources = Rails.env.production?
|
config.cache_sources = Rails.env.production?
|
||||||
config.include_json_root = false
|
config.include_json_root = false
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
de:
|
de:
|
||||||
about:
|
about:
|
||||||
about_mastodon: Mastodon ist ein <em>freier, quelloffener</em> soziales Netzwerkserver. Eine <em>dezentralisierte</em> Alternative zu kommerziellen Plattformen, verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am <em>sozialen Netzwerk</em> teilnehmen.
|
about_mastodon: Mastodon ist ein <em>freier, quelloffener</em> soziales Netzwerkserver. Als <em>dezentralisierte</em> Alternative zu kommerziellen Plattformen verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am <em>sozialen Netzwerk</em> teilnehmen.
|
||||||
get_started: Erste Schritte
|
get_started: Erste Schritte
|
||||||
source_code: Quellcode
|
source_code: Quellcode
|
||||||
terms: AGB
|
terms: AGB
|
||||||
accounts:
|
accounts:
|
||||||
follow: Folgen
|
follow: Folgen
|
||||||
followers: Folger
|
followers: Follower
|
||||||
following: Folgt
|
following: Gefolgt
|
||||||
nothing_here: Hier gibt es nichts!
|
nothing_here: Hier gibt es nichts!
|
||||||
people_followed_by: Nutzer, denen %{name} folgt
|
people_followed_by: Nutzer, denen %{name} folgt
|
||||||
people_who_follow: Nutzer, die %{name} folgen
|
people_who_follow: Nutzer, die %{name} folgen
|
||||||
@ -27,7 +27,7 @@ de:
|
|||||||
reset_password: Passwort zurücksetzen
|
reset_password: Passwort zurücksetzen
|
||||||
set_new_password: Neues Passwort setzen
|
set_new_password: Neues Passwort setzen
|
||||||
authorize_follow:
|
authorize_follow:
|
||||||
error: Das entfernte Profil konnte nicht geladen werden
|
error: Das Profil konnte nicht geladen werden
|
||||||
follow: Folgen
|
follow: Folgen
|
||||||
prompt_html: 'Du (<strong>%{self}</strong>) möchtest dieser Person folgen:'
|
prompt_html: 'Du (<strong>%{self}</strong>) möchtest dieser Person folgen:'
|
||||||
title: "%{acct} folgen"
|
title: "%{acct} folgen"
|
||||||
@ -55,25 +55,25 @@ de:
|
|||||||
notification_mailer:
|
notification_mailer:
|
||||||
favourite:
|
favourite:
|
||||||
body: 'Dein Beitrag wurde von %{name} favorisiert:'
|
body: 'Dein Beitrag wurde von %{name} favorisiert:'
|
||||||
subject: "%{name} hat deinen Beitrag favorisiert"
|
subject: "%{name} hat deinen Beitrag favorisiert."
|
||||||
follow:
|
follow:
|
||||||
body: "%{name} folgt dir jetzt!"
|
body: "%{name} folgt dir jetzt!"
|
||||||
subject: "%{name} folgt dir nun"
|
subject: "%{name} folgt dir jetzt."
|
||||||
follow_request:
|
follow_request:
|
||||||
body: "%{name} möchte dir folgen:"
|
body: "%{name} möchte dir folgen:"
|
||||||
subject: "%{name} möchte dir folgen"
|
subject: "%{name} möchte dir folgen."
|
||||||
mention:
|
mention:
|
||||||
body: "%{name} hat dich erwähnt:"
|
body: "%{name} hat dich erwähnt:"
|
||||||
subject: "%{name} hat dich erwähnt"
|
subject: "%{name} hat dich erwähnt."
|
||||||
reblog:
|
reblog:
|
||||||
body: 'Dein Beitrag wurde von %{name} geteilt:'
|
body: 'Dein Beitrag wurde von %{name} geteilt:'
|
||||||
subject: "%{name} teilte deinen Beitrag"
|
subject: "%{name} teilte deinen Beitrag."
|
||||||
pagination:
|
pagination:
|
||||||
next: Vorwärts
|
next: Vorwärts
|
||||||
prev: Zurück
|
prev: Zurück
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Dein Nutzername@Domain, von dem du dieser Person folgen möchtest
|
acct: Dein Nutzername@Domain, von dem aus du dieser Person folgen möchtest.
|
||||||
missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden
|
missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden.
|
||||||
proceed: Weiter
|
proceed: Weiter
|
||||||
prompt: 'Du wirst dieser Person folgen:'
|
prompt: 'Du wirst dieser Person folgen:'
|
||||||
settings:
|
settings:
|
||||||
|
@ -2,59 +2,59 @@
|
|||||||
de:
|
de:
|
||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: "Vielen Dank für Deine Registrierung. Bitte melde dich jetzt an."
|
confirmed: "Vielen Dank für deine Registrierung. Bitte melde dich jetzt an."
|
||||||
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail, mit der Du Deine Registrierung bestätigen kannst."
|
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail, mit der du deine Registrierung bestätigen kannst."
|
||||||
send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Du Deine Registrierung bestätigen kannst."
|
send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert, erhältst Du in wenigen Minuten eine E-Mail mit der du deine Registrierung bestätigen kannst."
|
||||||
failure:
|
failure:
|
||||||
already_authenticated: "Du bist bereits angemeldet."
|
already_authenticated: "Du bist bereits angemeldet."
|
||||||
inactive: "Dein Account ist nicht aktiv."
|
inactive: "Dein Account ist nicht aktiv."
|
||||||
invalid: "Ungültige Anmeldedaten."
|
invalid: "Ungültige Anmeldedaten."
|
||||||
last_attempt: "Du hast noch einen Versuch bevor dein Account gesperrt wird"
|
last_attempt: "Du hast noch einen Versuch bevor dein Account gesperrt wird."
|
||||||
locked: "Dein Account ist gesperrt."
|
locked: "Dein Account ist gesperrt."
|
||||||
not_found_in_database: "E-Mail-Adresse oder Passwort ungültig."
|
not_found_in_database: "E-Mail-Adresse oder Passwort ungültig."
|
||||||
timeout: "Deine Sitzung ist abgelaufen, bitte melde Dich erneut an."
|
timeout: "Deine Sitzung ist abgelaufen, bitte melde dich erneut an."
|
||||||
unauthenticated: "Du musst Dich anmelden oder registrieren, bevor Du fortfahren kannst."
|
unauthenticated: "Du musst Dich anmelden oder registrieren, bevor du fortfahren kannst."
|
||||||
unconfirmed: "Du musst Deinen Account bestätigen, bevor Du fortfahren kannst."
|
unconfirmed: "Du musst deinen Account bestätigen, bevor du fortfahren kannst."
|
||||||
mailer:
|
mailer:
|
||||||
confirmation_instructions:
|
confirmation_instructions:
|
||||||
subject: "Mastodon: Anleitung zur Bestätigung Deines Accounts"
|
subject: "Mastodon: Anleitung zur Bestätigung deines Accounts"
|
||||||
password_change:
|
password_change:
|
||||||
subject: 'Mastodon: Passwort wurde geändert'
|
subject: 'Mastodon: Passwort wurde geändert'
|
||||||
reset_password_instructions:
|
reset_password_instructions:
|
||||||
subject: "Mastodon: Anleitung um Dein Passwort zurückzusetzen"
|
subject: "Mastodon: Anleitung um dein Passwort zurückzusetzen"
|
||||||
unlock_instructions:
|
unlock_instructions:
|
||||||
subject: "Mastodon: Anleitung um Deinen Account freizuschalten"
|
subject: "Mastodon: Anleitung um deinen Account freizuschalten"
|
||||||
omniauth_callbacks:
|
omniauth_callbacks:
|
||||||
failure: "Du konntest nicht Deinem %{kind}-Account angemeldet werden, weil '%{reason}'."
|
failure: "Du konntest nicht mit deinem %{kind}-Account angemeldet werden, weil '%{reason}'."
|
||||||
success: "Du hast Dich erfolgreich mit Deinem %{kind}-Account angemeldet."
|
success: "Du hast dich erfolgreich mit Deinem %{kind}-Account angemeldet."
|
||||||
passwords:
|
passwords:
|
||||||
no_token: "Du kannst diese Seite nur von dem Link aus einer E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast stelle bitte sicher, dass du die vollständige Adresse aufrufst."
|
no_token: "Du kannst diese Seite nur über den Link aus der E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast, stelle bitte sicher, dass du die vollständige Adresse aufrufst."
|
||||||
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Dein Passwort zurücksetzen kannst."
|
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst."
|
||||||
send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Dein Passwort zurücksetzen können."
|
send_paranoid_instructions: "Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst."
|
||||||
updated: "Dein Passwort wurde geändert. Du bist jetzt angemeldet."
|
updated: "Dein Passwort wurde geändert. Du bist jetzt angemeldet."
|
||||||
updated_not_active: "Dein Passwort wurde geändert."
|
updated_not_active: "Dein Passwort wurde geändert."
|
||||||
registrations:
|
registrations:
|
||||||
destroyed: "Dein Account wurde gelöscht."
|
destroyed: "Dein Account wurde gelöscht."
|
||||||
signed_up: "Du hast dich erfolgreich registriert."
|
signed_up: "Du hast dich erfolgreich registriert."
|
||||||
signed_up_but_inactive: "Du hast dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account inaktiv ist."
|
signed_up_but_inactive: "Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account inaktiv ist."
|
||||||
signed_up_but_locked: "Du hast dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account gesperrt ist."
|
signed_up_but_locked: "Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account gesperrt ist."
|
||||||
signed_up_but_unconfirmed: "Du hast Dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail mit der Anleitung, wie Du Deinen Account freischalten kannst."
|
signed_up_but_unconfirmed: "Du hast Dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail mit der Anleitung, wie Du Deinen Account freischalten kannst."
|
||||||
update_needs_confirmation: "Deine Daten wurden aktualisiert, aber Du musst Deine neue E-Mail-Adresse bestätigen. Du erhälst in wenigen Minuten eine E-Mail, mit der Du die Änderung Deiner E-Mail-Adresse abschließen kannst."
|
update_needs_confirmation: "Deine Daten wurden aktualisiert, aber du musst deine neue E-Mail-Adresse bestätigen. Du erhälst in wenigen Minuten eine E-Mail, mit der du die Änderung deiner E-Mail-Adresse abschließen kannst."
|
||||||
updated: "Deine Daten wurden aktualisiert."
|
updated: "Deine Daten wurden aktualisiert."
|
||||||
sessions:
|
sessions:
|
||||||
already_signed_out: "Erfolgreich abgemeldet."
|
already_signed_out: "Erfolgreich abgemeldet."
|
||||||
signed_in: "Erfolgreich angemeldet."
|
signed_in: "Erfolgreich angemeldet."
|
||||||
signed_out: "Erfolgreich abgemeldet."
|
signed_out: "Erfolgreich abgemeldet."
|
||||||
unlocks:
|
unlocks:
|
||||||
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Deinen Account entsperren können."
|
send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren können."
|
||||||
send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Deinen Account entsperren kannst."
|
send_paranoid_instructions: "Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren kannst."
|
||||||
unlocked: "Dein Account wurde entsperrt. Du bist jetzt angemeldet."
|
unlocked: "Dein Account wurde entsperrt. Du bist jetzt angemeldet."
|
||||||
errors:
|
errors:
|
||||||
messages:
|
messages:
|
||||||
already_confirmed: "wurde bereits bestätigt"
|
already_confirmed: "wurde bereits bestätigt."
|
||||||
confirmation_period_expired: "muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an"
|
confirmation_period_expired: "muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an."
|
||||||
expired: "ist abgelaufen, bitte neu anfordern"
|
expired: "ist abgelaufen, bitte neu anfordern."
|
||||||
not_found: "nicht gefunden"
|
not_found: "wurde nicht gefunden."
|
||||||
not_locked: "ist nicht gesperrt"
|
not_locked: "ist nicht gesperrt"
|
||||||
not_saved:
|
not_saved:
|
||||||
one: "Konnte %{resource} nicht speichern: ein Fehler."
|
one: "Konnte %{resource} nicht speichern: ein Fehler."
|
||||||
|
61
config/locales/devise.fi.yml
Normal file
61
config/locales/devise.fi.yml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
fi:
|
||||||
|
devise:
|
||||||
|
confirmations:
|
||||||
|
confirmed: Sähköpostisi on onnistuneesti vahvistettu.
|
||||||
|
send_instructions: Saat kohta sähköpostiisi ohjeet kuinka voit aktivoida tilisi.
|
||||||
|
send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet sen varmentamiseen.
|
||||||
|
failure:
|
||||||
|
already_authenticated: Olet jo kirjautunut sisään.
|
||||||
|
inactive: Tiliäsi ei ole viellä aktivoitu.
|
||||||
|
invalid: Virheellinen %{authentication_keys} tai salasana.
|
||||||
|
last_attempt: Sinulla on yksi yritys jäljellä tai tili lukitaan.
|
||||||
|
locked: Tili on lukittu.
|
||||||
|
not_found_in_database: Virheellinen %{authentication_keys} tai salasana.
|
||||||
|
timeout: Sessiosi on umpeutunut. Kirjaudu sisään jatkaaksesi.
|
||||||
|
unauthenticated: Sinun tarvitsee kirjautua sisään tai rekisteröityä jatkaaksesi.
|
||||||
|
unconfirmed: Sinun tarvitsee varmentaa sähköpostisi jatkaaksesi.
|
||||||
|
mailer:
|
||||||
|
confirmation_instructions:
|
||||||
|
subject: 'Mastodon: Varmistus ohjeet'
|
||||||
|
password_change:
|
||||||
|
subject: 'Mastodon: Salasana vaihdettu'
|
||||||
|
reset_password_instructions:
|
||||||
|
subject: 'Mastodon: Salasanan vaihto ohjeet'
|
||||||
|
unlock_instructions:
|
||||||
|
subject: 'Mastodon: Avauksen ohjeet'
|
||||||
|
omniauth_callbacks:
|
||||||
|
failure: Varmennus %{kind} epäonnistui koska "%{reason}".
|
||||||
|
success: Onnistuneesti varmennettu %{kind} tilillä.
|
||||||
|
passwords:
|
||||||
|
no_token: Et pääse tälle sivulle ilman salasanan vaihto sähköpostia. Jos tulet tämmöisestä postista, varmista että sinulla on täydellinen URL.
|
||||||
|
send_instructions: Saat sähköpostitse ohjeet salasanan palautukseen muutaman minuutin kuluessa.
|
||||||
|
send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen.
|
||||||
|
updated: Salasanasi vaihdettu onnistuneesti. Olet nyt kirjautunut sisään.
|
||||||
|
updated_not_active: Salasanasi vaihdettu onnistuneesti.
|
||||||
|
registrations:
|
||||||
|
destroyed: Näkemiin! Tilisi on onnistuneesti peruttu. Toivottavasti näemme joskus uudestaan.
|
||||||
|
signed_up: Tervetuloa! Rekisteröitymisesi onnistu.
|
||||||
|
signed_up_but_inactive: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tiliäsi ei ole viellä aktivoitu.
|
||||||
|
signed_up_but_locked: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tilisi on lukittu.
|
||||||
|
signed_up_but_unconfirmed: Varmistuslinkki on lähetty sähköpostiisi. Seuraa sitä jotta tilisi voidaan aktivoida.
|
||||||
|
update_needs_confirmation: Tilisi on onnistuneesti päivitetty, mutta meidän tarvitsee vahvistaa sinun uusi sähköpostisi. Tarkista sähköpostisi ja seuraa viestissä tullutta linkkiä varmistaaksesi uuden osoitteen..
|
||||||
|
updated: Tilisi on onnistuneesti päivitetty.
|
||||||
|
sessions:
|
||||||
|
already_signed_out: Ulos kirjautuminen onnistui.
|
||||||
|
signed_in: Sisäänkirjautuminen onnistui.
|
||||||
|
signed_out: Ulos kirjautuminen onnistui.
|
||||||
|
unlocks:
|
||||||
|
send_instructions: Saat sähköpostiisi pian ohjeet, jolla voit avata tilisi uudestaan.
|
||||||
|
send_paranoid_instructions: Jos tilisi on olemassa, saat sähköpostiisi pian ohjeet tilisi avaamiseen.
|
||||||
|
unlocked: Tilisi on avattu onnistuneesti. Kirjaudu normaalisti sisään.
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
already_confirmed: on jo varmistettu. Yritä kirjautua sisään
|
||||||
|
confirmation_period_expired: pitää varmistaa %{period} sisällä, ole hyvä ja pyydä uusi
|
||||||
|
expired: on erääntynyt, ole hyvä ja pyydä uusi
|
||||||
|
not_found: ei löydy
|
||||||
|
not_locked: ei ollut lukittu
|
||||||
|
not_saved:
|
||||||
|
one: '1 virhe esti %{resource} tallennuksen:'
|
||||||
|
other: "%{count} virhettä esti %{resource} tallennuksen:"
|
61
config/locales/devise.no.yml
Normal file
61
config/locales/devise.no.yml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
'no':
|
||||||
|
devise:
|
||||||
|
confirmations:
|
||||||
|
confirmed: Epostaddressen din er blitt bekreftet.
|
||||||
|
send_instructions: Du vil motta en epost med instruksjoner for hvordan bekrefte din epostaddresse om noen få minutter.
|
||||||
|
send_paranoid_instructions: Hvis din epostaddresse finnes i vår database vil du motta en epost med instruksjoner for hvordan bekrefte din epost om noen få minutter.
|
||||||
|
failure:
|
||||||
|
already_authenticated: Du er allerede innlogget.
|
||||||
|
inactive: Din konto er ikke blitt aktivert ennå.
|
||||||
|
invalid: Ugyldig %{authentication_keys} eller passord.
|
||||||
|
last_attempt: Du har ett forsøk igjen før kontoen din bli låst.
|
||||||
|
locked: Din konto er låst.
|
||||||
|
not_found_in_database: Ugyldig %{authentication_keys} eller passord.
|
||||||
|
timeout: Sesjonen din løp ut på tid. Logg inn på nytt for å fortsette.
|
||||||
|
unauthenticated: Du må logge inn eller registrere deg før du kan fortsette.
|
||||||
|
unconfirmed: Du må bekrefte epostadressen din før du kan fortsette.
|
||||||
|
mailer:
|
||||||
|
confirmation_instructions:
|
||||||
|
subject: 'Mastodon: Instruksjoner for å bekrefte epostadresse'
|
||||||
|
password_change:
|
||||||
|
subject: 'Mastodon: Passord endret'
|
||||||
|
reset_password_instructions:
|
||||||
|
subject: 'Mastodon: Hvordan nullstille passord?'
|
||||||
|
unlock_instructions:
|
||||||
|
subject: 'Mastodon: Instruksjoner for å gjenåpne konto'
|
||||||
|
omniauth_callbacks:
|
||||||
|
failure: Kunne ikke autentisere deg fra %{kind} fordi "%{reason}".
|
||||||
|
success: Vellykket autentisering fra %{kind}.
|
||||||
|
passwords:
|
||||||
|
no_token: Du har ingen tilgang til denne siden så lenge du ikke kommer fra en epost om nullstilling av passord. Hvis du kommer fra en passordnullstilling epost, dobbelsjekk at du brukte hele URLen.
|
||||||
|
send_instructions: Du vil motta en epost med instruksjoner for å nullstille passordet ditt om noen få minutter.
|
||||||
|
send_paranoid_instructions: Hvis epostadressen din finnes i databasen vår vil du motta en instruksjonsmail om passord nullstilling om noen få minutter.
|
||||||
|
updated: Passordet ditt har blitt endret. Du er nå logget inn.
|
||||||
|
updated_not_active: Passordet ditt har blitt endret.
|
||||||
|
registrations:
|
||||||
|
destroyed: Adjø! Kontoen din har blitt avsluttet. Vi håper at vi ser deg igjen snart.
|
||||||
|
signed_up: Velkommen! Registrasjonen var vellykket.
|
||||||
|
signed_up_but_inactive: Registrasjonen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din ennå ikke har blitt aktivert.
|
||||||
|
signed_up_but_locked: Registrasjonen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din har blitt låst.
|
||||||
|
signed_up_but_unconfirmed: En epostmelding med en bekreftelseslink har blitt sendt til din adresse. Klikk på linken i eposten for å aktivere kontoen din.
|
||||||
|
update_needs_confirmation: Du har oppdatert kontoen din, men vi må bekrefte din nye epostadresse. Sjekk eposten din og følg bekreftelseslinken for å bekrefte din nye epostadresse.
|
||||||
|
updated: Kontoen din ble oppdatert.
|
||||||
|
sessions:
|
||||||
|
already_signed_out: Logget ut.
|
||||||
|
signed_in: Logget inn.
|
||||||
|
signed_out: Logget ut.
|
||||||
|
unlocks:
|
||||||
|
send_instructions: Du vil motta en epost med instruksjoner for å åpne kontoen din om noen få minutter.
|
||||||
|
send_paranoid_instructions: Hvis kontoen din eksisterer vil du motta en epost med instruksjoner for å åpne kontoen din om noen få minutter.
|
||||||
|
unlocked: Kontoen din ble åpnet uten problemer. Logg på for å fortsette.
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
already_confirmed: har allerede blitt bekreftet, prøv å logg på istedet.
|
||||||
|
confirmation_period_expired: må bekreftes innen %{period}. Spør om en ny bekreftelsesmail istedet.
|
||||||
|
expired: har utløpt, spør om en ny en istedet
|
||||||
|
not_found: ikke funnet
|
||||||
|
not_locked: var ikke låst
|
||||||
|
not_saved:
|
||||||
|
one: '1 feil hindret denne %{resource} fra å bli lagret:'
|
||||||
|
other: "%{count} feil hindret denne %{resource} fra å bli lagret:"
|
113
config/locales/doorkeeper.fi.yml
Normal file
113
config/locales/doorkeeper.fi.yml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
fi:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
doorkeeper/application:
|
||||||
|
name: Nimi
|
||||||
|
redirect_uri: Uudelleenohjaus URI
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
doorkeeper/application:
|
||||||
|
attributes:
|
||||||
|
redirect_uri:
|
||||||
|
fragment_present: ei voi sisältää osia.
|
||||||
|
invalid_uri: pitää olla validi URI.
|
||||||
|
relative_uri: pitää olla täydellinen URI.
|
||||||
|
secured_uri: pitää olla HTTPS/SSL URI.
|
||||||
|
doorkeeper:
|
||||||
|
applications:
|
||||||
|
buttons:
|
||||||
|
authorize: Valtuuta
|
||||||
|
cancel: Peruuta
|
||||||
|
destroy: Tuhoa
|
||||||
|
edit: Muokkaa
|
||||||
|
submit: Lähetä
|
||||||
|
confirmations:
|
||||||
|
destroy: Oletko varma?
|
||||||
|
edit:
|
||||||
|
title: Muokkaa applikaatiota
|
||||||
|
form:
|
||||||
|
error: Whoops! Tarkista lomakkeesi mahdollisten virheiden varalta
|
||||||
|
help:
|
||||||
|
native_redirect_uri: Käytä %{native_redirect_uri} paikallisiin testeihin
|
||||||
|
redirect_uri: Käytä yhtä riviä per URI
|
||||||
|
scopes: Erota scopet välilyönnein. Jätä tyhjäksi käyteksi oletus scopeja.
|
||||||
|
index:
|
||||||
|
callback_url: Callback URL
|
||||||
|
name: Nimi
|
||||||
|
new: Uusi applikaatio
|
||||||
|
title: Sinun applikaatiosi
|
||||||
|
new:
|
||||||
|
title: Uusi applikaatio
|
||||||
|
show:
|
||||||
|
actions: Toiminnot
|
||||||
|
application_id: Applikaation Id
|
||||||
|
callback_urls: Callback urls
|
||||||
|
scopes: Scopet
|
||||||
|
secret: Salainen avain
|
||||||
|
title: 'Applikaatio: %{name}'
|
||||||
|
authorizations:
|
||||||
|
buttons:
|
||||||
|
authorize: Valtuuta
|
||||||
|
deny: Evää
|
||||||
|
error:
|
||||||
|
title: Virhe on tapahtunut
|
||||||
|
new:
|
||||||
|
able_to: Se voi
|
||||||
|
prompt: Applikaatio %{client_name} pyytää lupaa tilillesi
|
||||||
|
title: Valtuutus vaaditaan
|
||||||
|
show:
|
||||||
|
title: Valtuutus koodi
|
||||||
|
authorized_applications:
|
||||||
|
buttons:
|
||||||
|
revoke: Evää
|
||||||
|
confirmations:
|
||||||
|
revoke: Oletko varma?
|
||||||
|
index:
|
||||||
|
application: Applikaatio
|
||||||
|
created_at: Valtuutettu
|
||||||
|
date_format: "%Y-%m-%d %H:%M:%S"
|
||||||
|
scopes: Scopet
|
||||||
|
title: Valtuuttamasi applikaatiot
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
access_denied: Resurssin omistaja tai valtuutus palvelin hylkäsi pyynnönr.
|
||||||
|
credential_flow_not_configured: Resurssin omistajan salasana epäonnistui koska Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu.
|
||||||
|
invalid_client: Asiakkaan valtuutus epäonnistui koska tuntematon asiakas, asiakas ei sisältänyt valtuutusta, tai tukematon valtuutus tapa
|
||||||
|
invalid_grant: Antamasi valtuutus lupa on joko väärä, erääntynyt, peruttu, ei vastaa uudelleenohjaus URI jota käytetään valtuutus pyynnössä, tai se myönnettin toiselle asiakkaalle.
|
||||||
|
invalid_redirect_uri: Uudelleenohjaus uri ei ole oikein.
|
||||||
|
invalid_request: Pyynnöstä puutti parametri, sisältää tukemattoman parametri arvonn, tai on korruptoitunut.
|
||||||
|
invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat väärät, tai resurssin omistajaa ei löydy
|
||||||
|
invalid_scope: Pyydetty scope on väärä, tuntemat, tai korruptoitunut.
|
||||||
|
invalid_token:
|
||||||
|
expired: Access token vanhentunut
|
||||||
|
revoked: Access token evätty
|
||||||
|
unknown: Access token väärä
|
||||||
|
resource_owner_authenticator_not_configured: Resurssin omistajan etsiminen epäonnistui koska Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu.
|
||||||
|
server_error: Valtuutus palvelin kohtasi odottamattoman virheen joka esti sitä täyttämästä pyyntöä.
|
||||||
|
temporarily_unavailable: Valtuutus palvelin ei voi tällä hetkellä käsitellä pyyntöäsi joko väliaikaisen ruuhkan tai huollon takia.
|
||||||
|
unauthorized_client: Asiakas ei ole valtuutettu tekemään tätä pyyntöä käyttäen tätä metodia.
|
||||||
|
unsupported_grant_type: Valtuutus grant type ei ole tuettu valtuutus palvelimella.
|
||||||
|
unsupported_response_type: Valtuutus palvelin ei tue tätä vastaus tyyppiä.
|
||||||
|
flash:
|
||||||
|
applications:
|
||||||
|
create:
|
||||||
|
notice: Applikaatio luotu.
|
||||||
|
destroy:
|
||||||
|
notice: Applikaatio poistettu.
|
||||||
|
update:
|
||||||
|
notice: Applikaatio päivitetty.
|
||||||
|
authorized_applications:
|
||||||
|
destroy:
|
||||||
|
notice: Applikaatio tuhottu.
|
||||||
|
layouts:
|
||||||
|
admin:
|
||||||
|
nav:
|
||||||
|
applications: Applikaatiot
|
||||||
|
oauth2_provider: OAuth2 Provider
|
||||||
|
application:
|
||||||
|
title: OAuth valtuutus tarvitaan
|
||||||
|
scopes:
|
||||||
|
follow: seuraa, estä, peru esto ja lopeta tilien seuraaminen
|
||||||
|
read: lukea tilin dataa
|
||||||
|
write: julkaista puolestasi
|
@ -72,19 +72,19 @@ fr:
|
|||||||
errors:
|
errors:
|
||||||
messages:
|
messages:
|
||||||
access_denied: Le propriétaire de la ressource ou le serveur d'autorisation a refusé la demande.
|
access_denied: Le propriétaire de la ressource ou le serveur d'autorisation a refusé la demande.
|
||||||
credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué en raison de Doorkeeper.configure.resource_owner_from_credentials n'est pas configuré.
|
credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n'est pas configuré.
|
||||||
invalid_client: L'authentification du client a échoué à cause d'un client inconnu, d'aucune authentification de client incluse, ou d'une méthode d'authentification non prise en charge.
|
invalid_client: L'authentification du client a échoué à cause d'un client inconnu, d'aucune authentification de client incluse, ou d'une méthode d'authentification non prise en charge.
|
||||||
invalid_grant: Le consentement d'autorisation accordé n'est pas valide, a expiré, est annulé, ne concorde pas avec l'URL de redirection utilisée dans la demande d'autorisation, ou a été émis à un autre client.
|
invalid_grant: Le consentement d'autorisation accordé n'est pas valide, a expiré, est annulé, ne concorde pas avec l'URL de redirection utilisée dans la demande d'autorisation, ou a été émis à un autre client.
|
||||||
invalid_redirect_uri: L'URL de redirection n'est pas valide.
|
invalid_redirect_uri: L'URL de redirection n'est pas valide.
|
||||||
invalid_request: La demande manque un paramètre requis, inclut une valeur de paramètre non prise en charge, ou est autrement mal formée.
|
invalid_request: La demande manque un paramètre requis, inclut une valeur de paramètre non prise en charge, ou est autrement mal formée.
|
||||||
invalid_resource_owner: Les identifiants fournis du propriétaire de la ressource ne sont pas valides, ou le propriétaire de la ressource ne peut être trouvé
|
invalid_resource_owner: Les identifiants fournis par le propriétaire de la ressource ne sont pas valides, ou le propriétaire de la ressource ne peut être trouvé
|
||||||
invalid_scope: La portée demandée n'est pas valide, est inconnue, ou est mal formée.
|
invalid_scope: La portée demandée n'est pas valide, est inconnue, ou est mal formée.
|
||||||
invalid_token:
|
invalid_token:
|
||||||
expired: Le jeton d'accès a expiré
|
expired: Le jeton d'accès a expiré
|
||||||
revoked: Le jeton d'accès a été révoqué
|
revoked: Le jeton d'accès a été révoqué
|
||||||
unknown: Le jeton d'accès n'est pas valide
|
unknown: Le jeton d'accès n'est pas valide
|
||||||
resource_owner_authenticator_not_configured: La recherche du propriétaire de la ressource a échoué en raison de Doorkeeper.configure.resource_owner_authenticator n'est pas configuré.
|
resource_owner_authenticator_not_configured: La recherche du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_authenticator n'est pas configuré.
|
||||||
server_error: Le serveur d'autorisation a rencontré une condition inattendue qui l'a empêché de remplir la demande.
|
server_error: Le serveur d'autorisation a rencontré une condition inattendue l'empêchant de remplir la demande.
|
||||||
temporarily_unavailable: Le serveur d'autorisation est actuellement incapable de traiter la demande à cause d'une surcharge ou d'un entretien temporaire du serveur.
|
temporarily_unavailable: Le serveur d'autorisation est actuellement incapable de traiter la demande à cause d'une surcharge ou d'un entretien temporaire du serveur.
|
||||||
unauthorized_client: Le client n'est pas autorisé à effectuer cette demande à l'aide de cette méthode.
|
unauthorized_client: Le client n'est pas autorisé à effectuer cette demande à l'aide de cette méthode.
|
||||||
unsupported_grant_type: Le type de consentement d'autorisation n'est pas pris en charge par le serveur d'autorisation.
|
unsupported_grant_type: Le type de consentement d'autorisation n'est pas pris en charge par le serveur d'autorisation.
|
||||||
|
113
config/locales/doorkeeper.no.yml
Normal file
113
config/locales/doorkeeper.no.yml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
'no':
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
doorkeeper/application:
|
||||||
|
name: Navn
|
||||||
|
redirect_uri: Omdirigerings-URI
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
doorkeeper/application:
|
||||||
|
attributes:
|
||||||
|
redirect_uri:
|
||||||
|
fragment_present: kan ikke inneholde ett fragment.
|
||||||
|
invalid_uri: må være en gyldig URI.
|
||||||
|
relative_uri: må være en absolutt URI.
|
||||||
|
secured_uri: må være en HTTPS/SSL URI.
|
||||||
|
doorkeeper:
|
||||||
|
applications:
|
||||||
|
buttons:
|
||||||
|
authorize: Autoriser
|
||||||
|
cancel: Avbryt
|
||||||
|
destroy: Ødelegg
|
||||||
|
edit: Endre
|
||||||
|
submit: Send inn
|
||||||
|
confirmations:
|
||||||
|
destroy: Er du sikker?
|
||||||
|
edit:
|
||||||
|
title: Endre applikasjon
|
||||||
|
form:
|
||||||
|
error: Whoops! Sjekk skjemaet ditt for mulige feil
|
||||||
|
help:
|
||||||
|
native_redirect_uri: Bruk %{native_redirect_uri} for lokale tester
|
||||||
|
redirect_uri: Bruk en linje per URI
|
||||||
|
scopes: Adskill omfang med mellomrom. La det være blankt for å bruke standard omfang.
|
||||||
|
index:
|
||||||
|
callback_url: Callback URL
|
||||||
|
name: Navn
|
||||||
|
new: Ny Applikasjon
|
||||||
|
title: Dine applikasjoner
|
||||||
|
new:
|
||||||
|
title: Ny Applikasjoner
|
||||||
|
show:
|
||||||
|
actions: Operasjoner
|
||||||
|
application_id: Applikasjon Id
|
||||||
|
callback_urls: Callback urls
|
||||||
|
scopes: Omfang
|
||||||
|
secret: Hemmelighet
|
||||||
|
title: 'Applikasjon: %{name}'
|
||||||
|
authorizations:
|
||||||
|
buttons:
|
||||||
|
authorize: Autoriser
|
||||||
|
deny: Avvis
|
||||||
|
error:
|
||||||
|
title: En feil oppsto
|
||||||
|
new:
|
||||||
|
able_to: Den vil ha mulighet til
|
||||||
|
prompt: Applikasjon %{client_name} spør om tilgang til din konto
|
||||||
|
title: Autorisasjon påkrevd
|
||||||
|
show:
|
||||||
|
title: Autoriserings kode
|
||||||
|
authorized_applications:
|
||||||
|
buttons:
|
||||||
|
revoke: Opphev
|
||||||
|
confirmations:
|
||||||
|
revoke: Opphev?
|
||||||
|
index:
|
||||||
|
application: Applikasjon
|
||||||
|
created_at: Autorisert
|
||||||
|
date_format: "%Y-%m-%d %H:%M:%S"
|
||||||
|
scopes: Omfang
|
||||||
|
title: Dine autoriserte applikasjoner
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
access_denied: Ressurseieren eller autoriserings tjeneren avviste forespørslen.
|
||||||
|
credential_flow_not_configured: Ressurseiers passord flyt feilet på grunn av at Doorkeeper.configure.resource_owner_from_credentials ikke var konfigurert.
|
||||||
|
invalid_client: Klient autentisering feilet på grunn av ukjent klient, ingen autentisering inkludert eller autentiserings metode som ikke er støttet.
|
||||||
|
invalid_grant: Autoriseringen er ugyldig, utløpt, opphevet, stemmer ikke overens med omdirigerings-URIen eller var utstedt til en annen klient.
|
||||||
|
invalid_redirect_uri: redirect urien som var inkludert er ikke gyldig.
|
||||||
|
invalid_request: Forespørslen mangler ett eller flere parametere, inkluderte ett parameter som ikke støttes eller har feil struktur.
|
||||||
|
invalid_resource_owner: Ressurseierens detaljer er ikke gyldig, eller så kan ikke eieren finnes.
|
||||||
|
invalid_scope: Det etterspurte omfanget er ugyldig, ukjent eller har feil struktur.
|
||||||
|
invalid_token:
|
||||||
|
expired: Tilgangsbeviset har utløpt
|
||||||
|
revoked: Tilgangsbeviset har blitt opphevet
|
||||||
|
unknown: Tilgangsbeviset er ugyldig
|
||||||
|
resource_owner_authenticator_not_configured: Ressurseier kunne ikke finnes fordi Doorkeeper.configure.resource_owner_authenticator ikke er konfigurert.
|
||||||
|
server_error: Autoriserings tjeneren støtte på en uventet hendelse som hindret den i å svare på forespørslen.
|
||||||
|
temporarily_unavailable: Autoriserings tjeneren kan ikke håndtere forespørslen grunnet en midlertidig overbelastning eller tjenervedlikehold.
|
||||||
|
unauthorized_client: Klienten har ikke autorisasjon for å utføre denne forespørslen med denne metoden.
|
||||||
|
unsupported_grant_type: Autorisasjons tildelings typen er ikke støttet av denne autoriserings tjeneren.
|
||||||
|
unsupported_response_type: Autorisasjons serveren støtter ikke denne typen av forespørsler.
|
||||||
|
flash:
|
||||||
|
applications:
|
||||||
|
create:
|
||||||
|
notice: Applikasjon opprettet.
|
||||||
|
destroy:
|
||||||
|
notice: Applikasjon slettet.
|
||||||
|
update:
|
||||||
|
notice: Applikasjon oppdatert.
|
||||||
|
authorized_applications:
|
||||||
|
destroy:
|
||||||
|
notice: Applikasjon opphevet.
|
||||||
|
layouts:
|
||||||
|
admin:
|
||||||
|
nav:
|
||||||
|
applications: Applikasjoner
|
||||||
|
oauth2_provider: OAuth2 tilbyder
|
||||||
|
application:
|
||||||
|
title: OAuth autorisering påkrevet
|
||||||
|
scopes:
|
||||||
|
follow: følg, blokker, avblokker, avfølg kontoer
|
||||||
|
read: lese dine data
|
||||||
|
write: poste på dine vegne
|
@ -5,6 +5,7 @@ en:
|
|||||||
about_this: About this instance
|
about_this: About this instance
|
||||||
apps: Apps
|
apps: Apps
|
||||||
business_email: 'Business e-mail:'
|
business_email: 'Business e-mail:'
|
||||||
|
closed_registrations: Registrations are currently closed on this instance.
|
||||||
contact: Contact
|
contact: Contact
|
||||||
description_headline: What is %{domain}?
|
description_headline: What is %{domain}?
|
||||||
domain_count_after: other instances
|
domain_count_after: other instances
|
||||||
|
164
config/locales/fi.yml
Normal file
164
config/locales/fi.yml
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
fi:
|
||||||
|
about:
|
||||||
|
about_mastodon: Mastodon on <em>ilmainen, avoimeen lähdekoodiin perustuva</em> sosiaalinen verkosto. <em>Hajautettu</em> vaihtoehto kaupallisille alustoille, se välttää eiskit yhden yrityksen monopolisoinnin sinun viestinnässäsi. Valitse palvelin mihin luotat — minkä tahansa valitset, voit vuorovaikuttaa muiden kanssa. Kuka tahansa voi luoda Mastodon palvelimen ja ottaa osaa <em>sosiaaliseen verkkoon</em> saumattomasti.
|
||||||
|
about_this: Tietoja tästä palvelimesta
|
||||||
|
apps: Ohjelmat
|
||||||
|
business_email: 'Business e-mail:'
|
||||||
|
contact: Ota yhteyttä
|
||||||
|
description_headline: Mikä on %{domain}?
|
||||||
|
domain_count_after: muut palvelimet
|
||||||
|
domain_count_before: Yhdistyneenä
|
||||||
|
features:
|
||||||
|
api: Avoin API ohjelmille ja palveluille
|
||||||
|
blocks: Rikkaat esto ja hiljennys työkalut
|
||||||
|
characters: 500 kirjainta per viesti
|
||||||
|
chronology: Aikajana on kronologisessa järjestyksessä
|
||||||
|
ethics: 'Eettinen suunnittelu: ei mainoksia, no seurantaa'
|
||||||
|
gifv: GIFV settejä ja lyhyitä videoita
|
||||||
|
privacy: Julkaisu kohtainen yksityisyys aseuts
|
||||||
|
public: Julkiset aikajanat
|
||||||
|
features_headline: Mikä erottaa Mastodonin muista
|
||||||
|
get_started: Aloita käyttö
|
||||||
|
links: Linkit
|
||||||
|
other_instances: muuhun palvelimeen
|
||||||
|
source_code: Lähdekoodi
|
||||||
|
status_count_after: statusta
|
||||||
|
status_count_before: Ovat luoneet
|
||||||
|
terms: Ehdot
|
||||||
|
user_count_after: käyttäjää
|
||||||
|
user_count_before: Koti käyttäjälle
|
||||||
|
accounts:
|
||||||
|
follow: Seuraa
|
||||||
|
followers: Seuraajat
|
||||||
|
following: Seuratut
|
||||||
|
nothing_here: Täällä ei ole mitään!
|
||||||
|
people_followed_by: Henkilöitä joita %{name} seuraa
|
||||||
|
people_who_follow: Henkilöt jotka seuraa %{name}
|
||||||
|
posts: Postaukset
|
||||||
|
remote_follow: Etäseuranta
|
||||||
|
unfollow: Lopeta seuraaminen
|
||||||
|
application_mailer:
|
||||||
|
settings: 'Muokkaa sähköposti asetuksia: %{link}'
|
||||||
|
signature: Mastodon ilmoituksia palvelimelta %{instance}
|
||||||
|
view: 'Katso:'
|
||||||
|
applications:
|
||||||
|
invalid_url: Annettu URL on väärä
|
||||||
|
auth:
|
||||||
|
change_password: Tunnukset
|
||||||
|
didnt_get_confirmation: Etkö saanut varmennus ohjeita?
|
||||||
|
forgot_password: Unohditko salasanasi?
|
||||||
|
login: Kirjaudu sisään
|
||||||
|
logout: Kirjaudu ulos
|
||||||
|
register: Rekisteröidy
|
||||||
|
resend_confirmation: Lähetä varmennus ohjeet uudestaan
|
||||||
|
reset_password: Palauta Salasana
|
||||||
|
set_new_password: Aseta uusi salasana
|
||||||
|
authorize_follow:
|
||||||
|
error: Valitettavasti tapahtui virhe etätilin haussa
|
||||||
|
follow: Seuraa
|
||||||
|
prompt_html: 'Sinä (<strong>%{self}</strong>) olet pyytänyt lupaa seurata:'
|
||||||
|
title: Seuraa %{acct}
|
||||||
|
datetime:
|
||||||
|
distance_in_words:
|
||||||
|
about_x_hours: "%{count}t"
|
||||||
|
about_x_months: "%{count}kk"
|
||||||
|
about_x_years: "%{count}v"
|
||||||
|
almost_x_years: "%{count}v"
|
||||||
|
half_a_minute: Juuri nyt
|
||||||
|
less_than_x_minutes: "%{count}m"
|
||||||
|
less_than_x_seconds: Juuri nyt
|
||||||
|
over_x_years: "%{count}v"
|
||||||
|
x_days: "%{count}pv"
|
||||||
|
x_minutes: "%{count}m"
|
||||||
|
x_months: "%{count}kk"
|
||||||
|
x_seconds: "%{count}s"
|
||||||
|
exports:
|
||||||
|
blocks: Estosi
|
||||||
|
csv: CSV
|
||||||
|
follows: Seurattavat
|
||||||
|
storage: Mediasi
|
||||||
|
generic:
|
||||||
|
changes_saved_msg: Muutokset onnistuneesti tallenettu!
|
||||||
|
powered_by: powered by %{link}
|
||||||
|
save_changes: Tallenna muutokset
|
||||||
|
validation_errors:
|
||||||
|
one: Jokin ei ole viellä oikein! Katso virhe alapuolelta
|
||||||
|
other: Jokin ei ole viellä oikein! Katso %{count} virhettä alapuolelta
|
||||||
|
imports:
|
||||||
|
preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella
|
||||||
|
success: Datasi on onnistuneesti ladattu ja käsitellään pian
|
||||||
|
types:
|
||||||
|
blocking: Estetyt lista
|
||||||
|
following: Seuratut lista
|
||||||
|
upload: Lähetä
|
||||||
|
landing_strip_html: <strong>%{name}</strong> on käyttäjä domainilla <strong>%{domain}</strong>. Voit seurata tai vuorovaikuttaa heidän kanssaan jos sinulla on tili yleisessä verkossa. Jos sinulla ei ole tiliä, voit <a href="%{sign_up_path}">rekisteröityä täällä</a>.
|
||||||
|
notification_mailer:
|
||||||
|
digest:
|
||||||
|
body: 'Tässä on pieni yhteenveto palvelimelta %{instance} viimeksi kun olit paikalla %{since}:'
|
||||||
|
mention: "%{name} mainitsi sinut:"
|
||||||
|
new_followers_summary:
|
||||||
|
one: Olet saanut yhden uuden seuraajan! Jee!
|
||||||
|
other: Olet saanut %{count} uutta seuraajaa! Loistavaa!
|
||||||
|
subject:
|
||||||
|
one: "1 uusi ilmoitus viimeisen käyntisi jälkeen \U0001F418"
|
||||||
|
other: "%{count} uutta ilmoitusta viimeisen käyntisi jälkeen \U0001F418"
|
||||||
|
favourite:
|
||||||
|
body: 'Statuksestasi tykkäsi %{name}:'
|
||||||
|
subject: "%{name} tykkäsi sinun statuksestasi"
|
||||||
|
follow:
|
||||||
|
body: "%{name} seuraa nyt sinua!"
|
||||||
|
subject: "%{name} seuraa nyt sinua"
|
||||||
|
follow_request:
|
||||||
|
body: "%{name} on pyytänyt seurata sinua"
|
||||||
|
subject: 'Odottava seuraus pyyntö: %{name}'
|
||||||
|
mention:
|
||||||
|
body: 'Sinut mainitsi %{name} postauksessa:'
|
||||||
|
subject: Sinut mainitsi %{name}
|
||||||
|
reblog:
|
||||||
|
body: 'Sinun statustasi boostasi %{name}:'
|
||||||
|
subject: "%{name} boostasi statustasi"
|
||||||
|
pagination:
|
||||||
|
next: Seuraava
|
||||||
|
prev: Edellinen
|
||||||
|
remote_follow:
|
||||||
|
acct: Syötä sinun käyttäjänimesi@domain jos haluat seurata palvelimelta
|
||||||
|
missing_resource: Ei löydetty tarvittavaa uudelleenohjaavaa URL-linkkiä tilillesi
|
||||||
|
proceed: Siirry seuraamiseen
|
||||||
|
prompt: 'Sinä aiot seurata:'
|
||||||
|
settings:
|
||||||
|
authorized_apps: Valtuutetut ohjelmat
|
||||||
|
back: Takaisin Mastodoniin
|
||||||
|
edit_profile: Muokkaa profiilia
|
||||||
|
export: Datan vienti
|
||||||
|
import: Datan tuonti
|
||||||
|
preferences: Ominaisuudet
|
||||||
|
settings: Asetukset
|
||||||
|
two_factor_auth: Kaksivaiheinen tunnistus
|
||||||
|
statuses:
|
||||||
|
open_in_web: Avaa webissä
|
||||||
|
over_character_limit: sallittu kirjanmäärä %{max} ylitetty
|
||||||
|
show_more: Näytä lisää
|
||||||
|
visibilities:
|
||||||
|
private: Näytä vain seuraajille
|
||||||
|
public: Julkinen
|
||||||
|
unlisted: Julkinen, mutta älä näytä julkisella aikajanalla
|
||||||
|
stream_entries:
|
||||||
|
click_to_show: Klikkaa näyttääksesi
|
||||||
|
reblogged: boosted
|
||||||
|
sensitive_content: Herkkä materiaali
|
||||||
|
time:
|
||||||
|
formats:
|
||||||
|
default: "%b %d, %Y, %H:%M"
|
||||||
|
two_factor_auth:
|
||||||
|
description_html: Jos otat käyttöön <strong>kaksivaiheisen tunnistuksen</stron>, kirjautumiseen vaaditaan puhelin, joka voi generoida tokeneita kirjautumista varten.
|
||||||
|
disable: Poista käytöstä
|
||||||
|
enable: Ota käyttöön
|
||||||
|
instructions_html: "<strong>Skannaa tämä QR-koodi Google Authenticator tai samanlaiseen sovellukseen puhelimellasi</strong>. Tästä hetkestä lähtien ohjelma generoi koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa."
|
||||||
|
plaintext_secret_html: 'Plain-text secret: <samp>%{secret}</samp>'
|
||||||
|
warning: Jos et juuri nyt voi konfiguroida authenticator-applikaatiota juuri nyt, sinun pitäisi klikata "Poista käytöstä" tai et voi kirjautua sisään.
|
||||||
|
users:
|
||||||
|
invalid_email: Virheellinen sähköposti
|
||||||
|
invalid_otp_token: Virheellinen kaksivaihe tunnistus koodi
|
||||||
|
will_paginate:
|
||||||
|
page_gap: "…"
|
@ -5,6 +5,7 @@ fr:
|
|||||||
about_this: À propos de cette instance
|
about_this: À propos de cette instance
|
||||||
apps: Applications
|
apps: Applications
|
||||||
business_email: E-mail professionnel
|
business_email: E-mail professionnel
|
||||||
|
closed_registrations: Les inscriptions sont actuellement fermées sur cette instance. .
|
||||||
description_headline: Qu'est-ce que %{domain} ?
|
description_headline: Qu'est-ce que %{domain} ?
|
||||||
domain_count_after: autres instances
|
domain_count_after: autres instances
|
||||||
domain_count_before: Connectés à
|
domain_count_before: Connectés à
|
||||||
|
164
config/locales/no.yml
Normal file
164
config/locales/no.yml
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
'no':
|
||||||
|
about:
|
||||||
|
about_mastodon: Mastodon er et <em>gratis, åpen kildekode</em> sosialt nettverk. Et <em>desentralisert</em> alternativ til kommersielle plattformer. Slik kan det unngå risikoene ved å ha et enkelt selskap med monopol på din kommunikasjon. Velg en tjener du stoler på — uansett hvilken du velger så kan du interagere med alle andre. Alle kan kjøre sin egen Mastodon og delta sømløst i det sosiale nettverket.
|
||||||
|
about_this: Om denne instansen
|
||||||
|
apps: Applikasjoner
|
||||||
|
business_email: 'Bedriftsepost:'
|
||||||
|
contact: Kontakt
|
||||||
|
description_headline: Hva er %{domain}?
|
||||||
|
domain_count_after: andre instanser
|
||||||
|
domain_count_before: Koblet til
|
||||||
|
features:
|
||||||
|
api: Åpent api for applikasjoner og tjenester
|
||||||
|
blocks: Rikholdige blokkerings verktøy
|
||||||
|
characters: 500 tegn per post
|
||||||
|
chronology: Tidslinjer er kronologiske
|
||||||
|
ethics: 'Etisk design: Ingen reklame, ingen sporing'
|
||||||
|
gifv: GIFV sett og korte videoer
|
||||||
|
privacy: Finmaskete personvernsinnstillinger
|
||||||
|
public: Offentlige tidslinjer
|
||||||
|
features_headline: Hva skiller Mastodon fra andre sosiale nettverk
|
||||||
|
get_started: Kom i gang
|
||||||
|
links: Lenker
|
||||||
|
other_instances: Andre instanser
|
||||||
|
source_code: Kildekode
|
||||||
|
status_count_after: statuser
|
||||||
|
status_count_before: Hvem skrev
|
||||||
|
terms: Betingelser
|
||||||
|
user_count_after: brukere
|
||||||
|
user_count_before: Hjem til
|
||||||
|
accounts:
|
||||||
|
follow: Følg
|
||||||
|
followers: Følgere
|
||||||
|
following: Følger
|
||||||
|
nothing_here: Det er ingenting her!
|
||||||
|
people_followed_by: Folk som %{name} følger
|
||||||
|
people_who_follow: Folk som følger %{name}
|
||||||
|
posts: Poster
|
||||||
|
remote_follow: Følg fra andre instanser
|
||||||
|
unfollow: Avfølg
|
||||||
|
application_mailer:
|
||||||
|
settings: 'Endre foretrukne epost innstillinger: %{link}'
|
||||||
|
signature: Mastodon notiser fra %{instance}
|
||||||
|
view: 'Se:'
|
||||||
|
applications:
|
||||||
|
invalid_url: Den oppgitte URLen er ugyldig
|
||||||
|
auth:
|
||||||
|
change_password: Brukerdetaljer
|
||||||
|
didnt_get_confirmation: Fikk du ikke bekreftelsesmailen din?
|
||||||
|
forgot_password: Har du glemt passordet ditt?
|
||||||
|
login: Innlogging
|
||||||
|
logout: Logg ut
|
||||||
|
register: Bli med
|
||||||
|
resend_confirmation: Send bekreftelsesinstruksjoner på nytt
|
||||||
|
reset_password: Nullstill passord
|
||||||
|
set_new_password: Sett nytt passord
|
||||||
|
authorize_follow:
|
||||||
|
error: Uheldigvis så skjedde det en feil når vi prøvde å få tak i en konto fra en annen instans.
|
||||||
|
follow: Følg
|
||||||
|
prompt_html: 'Du (<strong>%{self}</strong>) har spurt om å følge:'
|
||||||
|
title: Følg %{acct}
|
||||||
|
datetime:
|
||||||
|
distance_in_words:
|
||||||
|
about_x_hours: "%{count}t"
|
||||||
|
about_x_months: "%{count}m"
|
||||||
|
about_x_years: "%{count}å"
|
||||||
|
almost_x_years: "%{count}å"
|
||||||
|
half_a_minute: Nylig
|
||||||
|
less_than_x_minutes: "%{count}min"
|
||||||
|
less_than_x_seconds: Nylig
|
||||||
|
over_x_years: "%{count}å"
|
||||||
|
x_days: "%{count}d"
|
||||||
|
x_minutes: "%{count}min"
|
||||||
|
x_months: "%{count}mo"
|
||||||
|
x_seconds: "%{count}s"
|
||||||
|
exports:
|
||||||
|
blocks: Du blokkerer
|
||||||
|
csv: CSV
|
||||||
|
follows: Du følger
|
||||||
|
storage: Media lagring
|
||||||
|
generic:
|
||||||
|
changes_saved_msg: Vellykket lagring av endringer!
|
||||||
|
powered_by: drevet av %{link}
|
||||||
|
save_changes: Lagre endringer
|
||||||
|
validation_errors:
|
||||||
|
one: Noe er ikke helt riktig ennå. Vær snill å se etter en gang til
|
||||||
|
other: Noe er ikke helt riktig ennå. Det er ennå %{count} feil å rette på
|
||||||
|
imports:
|
||||||
|
preface: Du kan importere data om mennesker du følger eller blokkerer inn til kontoen din på denne instansen, fra filer opprettet av eksporter fra andre instanser.
|
||||||
|
success: Din data ble mottatt og vil bli prosessert så fort som mulig.
|
||||||
|
types:
|
||||||
|
blocking: Blokkeringsliste
|
||||||
|
following: Følgeliste
|
||||||
|
upload: Opplastning
|
||||||
|
landing_strip_html: <strong>%{name}</strong> er en bruker på <strong>%{domain}</strong>. Du kan følge dem eller interagere med dem hvis du har en konto hvor som helst i fediverset. Hvis du ikke har en konto så kan du <a href="%{sign_up_path}">registrere deg her</a>.
|
||||||
|
notification_mailer:
|
||||||
|
digest:
|
||||||
|
body: 'Her er en kort oppsummering av hva du har gått glipp av på %{instance} siden du logget deg inn sist den %{since}:'
|
||||||
|
mention: "%{name} nevnte deg i:"
|
||||||
|
new_followers_summary:
|
||||||
|
one: Du har fått en ny følger. Jippi!
|
||||||
|
other: Du har fått %{count} nye følgere! Imponerende!
|
||||||
|
subject:
|
||||||
|
one: "1 ny hendelse siden ditt siste besøk \U0001F418"
|
||||||
|
other: "%{count} nye hendelser siden ditt siste besøk \U0001F418"
|
||||||
|
favourite:
|
||||||
|
body: 'Din status ble satt som favoritt av %{name}'
|
||||||
|
subject: "%{name} satte din status som favoritt."
|
||||||
|
follow:
|
||||||
|
body: "%{name} følger deg!"
|
||||||
|
subject: "%{name} følger deg"
|
||||||
|
follow_request:
|
||||||
|
body: "%{name} har spurt om å få lov til å følge deg"
|
||||||
|
subject: 'Ventende følger: %{name}'
|
||||||
|
mention:
|
||||||
|
body: 'Du ble nevnt av %{name} i:'
|
||||||
|
subject: Du ble nevnt av %{name}
|
||||||
|
reblog:
|
||||||
|
body: 'Din status fikk en boost av %{name}:'
|
||||||
|
subject: "%{name} ga din status en boost"
|
||||||
|
pagination:
|
||||||
|
next: Neste
|
||||||
|
prev: Forrige
|
||||||
|
remote_follow:
|
||||||
|
acct: Tast inn brukernavn@domene som du vil følge fra
|
||||||
|
missing_resource: Kunne ikke finne URLen for din konto
|
||||||
|
proceed: Fortsett med følging
|
||||||
|
prompt: 'Du kommer til å følge:'
|
||||||
|
settings:
|
||||||
|
authorized_apps: Autoriserte applikasjoner
|
||||||
|
back: Tilbake til Mastodon
|
||||||
|
edit_profile: Endre profil
|
||||||
|
export: Data eksport
|
||||||
|
import: Importer
|
||||||
|
preferences: Foretrukne valg
|
||||||
|
settings: Innstillinger
|
||||||
|
two_factor_auth: To-faktor autentisering
|
||||||
|
statuses:
|
||||||
|
open_in_web: Åpne i nettleser
|
||||||
|
over_character_limit: tegngrense på %{max} overskredet
|
||||||
|
show_more: Vis mer
|
||||||
|
visibilities:
|
||||||
|
private: Vis kun til følgere
|
||||||
|
public: Offentlig
|
||||||
|
unlisted: Offentlig, men vis ikke på offentlig tidslinje
|
||||||
|
stream_entries:
|
||||||
|
click_to_show: Klikk for å vise
|
||||||
|
reblogged: boostet
|
||||||
|
sensitive_content: Sensitivt innhold
|
||||||
|
time:
|
||||||
|
formats:
|
||||||
|
default: "%d, %b %Y, %H:%M"
|
||||||
|
two_factor_auth:
|
||||||
|
description_html: Hvis du skru på <strong>tofaktor autentisering</strong> vil innlogging kreve at du har telefonen din, som vil generere koder som du må taste inn.
|
||||||
|
disable: Skru av
|
||||||
|
enable: Skru på
|
||||||
|
instructions_html: "<strong>Scan denne QR-koden i Google Authenticator eller en lignende app på telefonen din</strong>. Fra nå av så vil denne applikasjonen generere koder for deg som skal brukes under innlogging"
|
||||||
|
plaintext_secret_html: 'Plain-text secret: <samp>%{secret}</samp>'
|
||||||
|
warning: Hvis du ikke kan konfigurere en autentikatorapp nå, så bør du trykke "Skru av"; ellers vil du ikke kunne logge inn.
|
||||||
|
users:
|
||||||
|
invalid_email: E-post addressen er ugyldig
|
||||||
|
invalid_otp_token: Ugyldig two-faktor kode
|
||||||
|
will_paginate:
|
||||||
|
page_gap: "…"
|
@ -38,7 +38,7 @@ en:
|
|||||||
follow: Send e-mail when someone follows you
|
follow: Send e-mail when someone follows you
|
||||||
follow_request: Send e-mail when someone requests to follow you
|
follow_request: Send e-mail when someone requests to follow you
|
||||||
mention: Send e-mail when someone mentions you
|
mention: Send e-mail when someone mentions you
|
||||||
reblog: Send e-mail when someone reblogs your status
|
reblog: Send e-mail when someone boosts your status
|
||||||
'no': 'No'
|
'no': 'No'
|
||||||
required:
|
required:
|
||||||
mark: "*"
|
mark: "*"
|
||||||
|
46
config/locales/simple_form.fi.yml
Normal file
46
config/locales/simple_form.fi.yml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
fi:
|
||||||
|
simple_form:
|
||||||
|
hints:
|
||||||
|
defaults:
|
||||||
|
avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 120x120px
|
||||||
|
display_name: Korkeintaan 30 merkkiä
|
||||||
|
header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px
|
||||||
|
locked: Vaatii sinun manuaalisesti hyväksymään seuraajat ja asettaa julkaisujen yksityisyyden vain seuraajille
|
||||||
|
note: Korkeintaan 160 merkkiä
|
||||||
|
imports:
|
||||||
|
data: CSV tiedosto tuotu toiselta Mastodon palvelimelta
|
||||||
|
labels:
|
||||||
|
defaults:
|
||||||
|
avatar: Avatar
|
||||||
|
confirm_new_password: Varmista uusi salasana
|
||||||
|
confirm_password: Varmista salasana
|
||||||
|
current_password: Nykyinen salasana
|
||||||
|
data: Data
|
||||||
|
display_name: Näykyvä nimi
|
||||||
|
email: Sähköpostiosoite
|
||||||
|
header: Header
|
||||||
|
locale: Kieli
|
||||||
|
locked: Tee tilistä yksityinen
|
||||||
|
new_password: Uusi salasana
|
||||||
|
note: Bio
|
||||||
|
otp_attempt: Kaksivaiheinen koodi
|
||||||
|
password: Salasana
|
||||||
|
setting_default_privacy: Julkaisun yksityisyys
|
||||||
|
type: Tuonti tyyppi
|
||||||
|
username: Käyttäjänimi
|
||||||
|
interactions:
|
||||||
|
must_be_follower: Estä ilmoitukset käyttäjiltä jotka eivät seuraa sinua
|
||||||
|
must_be_following: Estä ilmoitukset käyttäjiltä joita et seuraa
|
||||||
|
notification_emails:
|
||||||
|
digest: Send digest e-mails
|
||||||
|
favourite: Lähetä s-posti kun joku tykkää statuksestasi
|
||||||
|
follow: Lähetä s-posti kun joku seuraa sinua
|
||||||
|
follow_request: Lähetä s-posti kun joku pyytää seurata sinua
|
||||||
|
mention: Lähetä s-posti kun joku mainitsee sinut
|
||||||
|
reblog: Lähetä s-posti kun joku buustaa julkaisusi
|
||||||
|
'no': 'Ei'
|
||||||
|
required:
|
||||||
|
mark: "*"
|
||||||
|
text: vaaditaan
|
||||||
|
'yes': 'Kyllä'
|
46
config/locales/simple_form.no.yml
Normal file
46
config/locales/simple_form.no.yml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
'no':
|
||||||
|
simple_form:
|
||||||
|
hints:
|
||||||
|
defaults:
|
||||||
|
avatar: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 120x120px
|
||||||
|
display_name: Maksimalt 30 tegn
|
||||||
|
header: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 700x335px
|
||||||
|
locked: Krever at du manuelt godkjenner følgere og setter standard beskyttelse av poster til kun-følgere
|
||||||
|
note: Maksimalt 160 tegn
|
||||||
|
imports:
|
||||||
|
data: CSV fil eksportert fra en annen Mastodon instans
|
||||||
|
labels:
|
||||||
|
defaults:
|
||||||
|
avatar: Avatar
|
||||||
|
confirm_new_password: Bekreft nytt passord
|
||||||
|
confirm_password: Bekreft passord
|
||||||
|
current_password: Nåværende passord
|
||||||
|
data: Data
|
||||||
|
display_name: Visningsnavn
|
||||||
|
email: E-post adresse
|
||||||
|
header: Header
|
||||||
|
locale: Språk
|
||||||
|
locked: Endre konto til privat
|
||||||
|
new_password: Nytt passord
|
||||||
|
note: Biografi
|
||||||
|
otp_attempt: To-faktor kode
|
||||||
|
password: Passord
|
||||||
|
setting_default_privacy: Leserettigheter for poster
|
||||||
|
type: Importeringstype
|
||||||
|
username: Brukernavn
|
||||||
|
interactions:
|
||||||
|
must_be_follower: Blokker meldinger fra ikke-følgere
|
||||||
|
must_be_following: Blokker meldinger fra folk du ikke følger
|
||||||
|
notification_emails:
|
||||||
|
digest: Send oppsummerings eposter
|
||||||
|
favourite: Send e-post når noen setter din status som favoritt
|
||||||
|
follow: Send e-post når noen følger deg
|
||||||
|
follow_request: Send e-post når noen spør om å få følge deg
|
||||||
|
mention: Send e-post når noen nevner deg
|
||||||
|
reblog: Send e-post når noen reblogger din status
|
||||||
|
'no': 'Nei'
|
||||||
|
required:
|
||||||
|
mark: "*"
|
||||||
|
text: påkrevd
|
||||||
|
'yes': 'Ja'
|
@ -14,11 +14,11 @@ SimpleNavigation::Configuration.run do |navigation|
|
|||||||
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
|
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
|
||||||
end
|
end
|
||||||
|
|
||||||
primary.item :admin, safe_join([fa_icon('cogs fw'), 'Administration']), admin_accounts_url, if: proc { current_user.admin? } do |admin|
|
primary.item :admin, safe_join([fa_icon('cogs fw'), 'Administration']), admin_reports_url, if: proc { current_user.admin? } do |admin|
|
||||||
admin.item :reports, safe_join([fa_icon('flag fw'), 'Reports']), admin_reports_url, highlights_on: %r{/admin/reports}
|
admin.item :reports, safe_join([fa_icon('flag fw'), 'Reports']), admin_reports_url, highlights_on: %r{/admin/reports}
|
||||||
admin.item :accounts, safe_join([fa_icon('users fw'), 'Accounts']), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
admin.item :accounts, safe_join([fa_icon('users fw'), 'Accounts']), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
||||||
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), 'PubSubHubbub']), admin_pubsubhubbub_index_url
|
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), 'PubSubHubbub']), admin_pubsubhubbub_index_url
|
||||||
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), 'Domain Blocks']), admin_domain_blocks_url
|
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), 'Domain Blocks']), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
|
||||||
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url
|
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url
|
||||||
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url
|
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url
|
||||||
admin.item :settings, safe_join([fa_icon('cogs fw'), 'Site Settings']), admin_settings_url
|
admin.item :settings, safe_join([fa_icon('cogs fw'), 'Site Settings']), admin_settings_url
|
||||||
|
@ -77,7 +77,7 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :admin do
|
namespace :admin do
|
||||||
resources :pubsubhubbub, only: [:index]
|
resources :pubsubhubbub, only: [:index]
|
||||||
resources :domain_blocks, only: [:index, :create]
|
resources :domain_blocks, only: [:index, :new, :create]
|
||||||
resources :settings, only: [:index, :update]
|
resources :settings, only: [:index, :update]
|
||||||
|
|
||||||
resources :reports, only: [:index, :show] do
|
resources :reports, only: [:index, :show] do
|
||||||
|
@ -5,6 +5,8 @@ defaults: &defaults
|
|||||||
site_extended_description: ''
|
site_extended_description: ''
|
||||||
site_contact_username: ''
|
site_contact_username: ''
|
||||||
site_contact_email: ''
|
site_contact_email: ''
|
||||||
|
open_registrations: true
|
||||||
|
closed_registrations_message: ''
|
||||||
notification_emails:
|
notification_emails:
|
||||||
follow: false
|
follow: false
|
||||||
reblog: false
|
reblog: false
|
||||||
@ -15,6 +17,7 @@ defaults: &defaults
|
|||||||
interactions:
|
interactions:
|
||||||
must_be_follower: false
|
must_be_follower: false
|
||||||
must_be_following: false
|
must_be_following: false
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
class AddActionTakenByAccountIdToReports < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
add_column :reports, :action_taken_by_account_id, :integer
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,5 @@
|
|||||||
|
class AddIndexOnMentionsStatusId < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
add_index :mentions, :status_id
|
||||||
|
end
|
||||||
|
end
|
@ -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: 20170330164118) do
|
ActiveRecord::Schema.define(version: 20170405112956) 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"
|
||||||
@ -127,6 +127,7 @@ ActiveRecord::Schema.define(version: 20170330164118) do
|
|||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
|
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
|
||||||
|
t.index ["status_id"], name: "index_mentions_on_status_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "mutes", force: :cascade do |t|
|
create_table "mutes", force: :cascade do |t|
|
||||||
@ -208,6 +209,7 @@ ActiveRecord::Schema.define(version: 20170330164118) do
|
|||||||
t.boolean "action_taken", default: false, null: false
|
t.boolean "action_taken", default: false, null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.integer "action_taken_by_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "settings", force: :cascade do |t|
|
create_table "settings", force: :cascade do |t|
|
||||||
|
@ -33,7 +33,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
build: .
|
build: .
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq -q default -q mailers -q push
|
command: bundle exec sidekiq -q default -q mailers -q pull -q push
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
|
@ -7,7 +7,7 @@ So, you have a working Mastodon instance... now what?
|
|||||||
|
|
||||||
The following rake task:
|
The following rake task:
|
||||||
|
|
||||||
rails mastodon:make_admin USERNAME=alice
|
rake mastodon:make_admin USERNAME=alice
|
||||||
|
|
||||||
Would turn the local user "alice" into an admin.
|
Would turn the local user "alice" into an admin.
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.co
|
|||||||
1. Click the above button.
|
1. Click the above button.
|
||||||
2. Fill in the options requested.
|
2. Fill in the options requested.
|
||||||
* You can use a .herokuapp.com domain, which will be simple to set up, or you can use a custom domain. If you want a custom domain and HTTPS, you will need to upgrade to a paid plan (to use Heroku's SSL features), or set up [CloudFlare](https://cloudflare.com) who offer free "Flexible SSL" (note: CloudFlare have some undefined limits on WebSockets. So far, no one has reported hitting concurrent connection limits).
|
* You can use a .herokuapp.com domain, which will be simple to set up, or you can use a custom domain. If you want a custom domain and HTTPS, you will need to upgrade to a paid plan (to use Heroku's SSL features), or set up [CloudFlare](https://cloudflare.com) who offer free "Flexible SSL" (note: CloudFlare have some undefined limits on WebSockets. So far, no one has reported hitting concurrent connection limits).
|
||||||
* You will want Amazon S3 for file storage. The only exception is for development purposes, where you may not care if files are not saaved. Follow a guide online for creating a free Amazon S3 bucket and Access Key, then enter the details.
|
* You will want Amazon S3 for file storage. The only exception is for development purposes, where you may not care if files are not saved. Follow a guide online for creating a free Amazon S3 bucket and Access Key, then enter the details.
|
||||||
* If you want your Mastodon to be able to send emails, configure SMTP settings here (or later). Consider using [Mailgun](https://mailgun.com) or similar, who offer free plans that should suit your interests.
|
* If you want your Mastodon to be able to send emails, configure SMTP settings here (or later). Consider using [Mailgun](https://mailgun.com) or similar, who offer free plans that should suit your interests.
|
||||||
3. Deploy! The app should be set up, with a working web interface and database. You can change settings and manage versions from the Heroku dashboard.
|
3. Deploy! The app should be set up, with a working web interface and database. You can change settings and manage versions from the Heroku dashboard.
|
||||||
|
|
||||||
|
You may need to use the `heroku` CLI application to run `USERNAME=yourUsername rails mastodon:make_admin` to make yourself an admin.
|
||||||
|
@ -11,10 +11,23 @@ map $http_upgrade $connection_upgrade {
|
|||||||
'' close;
|
'' close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name example.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name example.com;
|
server_name example.com;
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2;
|
||||||
|
ssl_ciphers EECDH+AESGCM:EECDH+AES;
|
||||||
|
ssl_ecdh_curve secp384r1;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||||
|
|
||||||
@ -75,8 +88,9 @@ It is recommended to create a special user for mastodon on the server (you could
|
|||||||
|
|
||||||
## General dependencies
|
## General dependencies
|
||||||
|
|
||||||
|
sudo apt-get install imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev nodejs file git curl
|
||||||
curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
|
curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
|
||||||
sudo apt-get install imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev nodejs
|
apt-get intall nodejs
|
||||||
sudo npm install -g yarn
|
sudo npm install -g yarn
|
||||||
|
|
||||||
## Redis
|
## Redis
|
||||||
@ -112,7 +126,7 @@ Then once `rbenv` is ready, run `rbenv install 2.3.1` to install the Ruby versio
|
|||||||
You need the `git-core` package installed on your system. If it is so, from the `mastodon` user:
|
You need the `git-core` package installed on your system. If it is so, from the `mastodon` user:
|
||||||
|
|
||||||
cd ~
|
cd ~
|
||||||
git clone https://github.com/Gargron/mastodon.git live
|
git clone https://github.com/tootsuite/mastodon.git live
|
||||||
cd live
|
cd live
|
||||||
|
|
||||||
Then you can proceed to install project dependencies:
|
Then you can proceed to install project dependencies:
|
||||||
@ -132,7 +146,7 @@ Fill in the important data, like host/port of the redis database, host/port/user
|
|||||||
|
|
||||||
rake secret
|
rake secret
|
||||||
|
|
||||||
To get a random string. If you are setting up on one single server (most likely), then REDIS_HOST is localhost and `DB_HOST` is `/var/run/postgresql`, `DB_USER` is `mastodon` and `DB_NAME` is `mastodon_production` while `DB_PASS` is empty because this setup will use the ident authentication method (system user "mastodon" maps to postgres user "mastodon").
|
To get a random string. If you are setting up on one single server (most likely), then `REDIS_HOST` is localhost and `DB_HOST` is `/var/run/postgresql`, `DB_USER` is `mastodon` and `DB_NAME` is `mastodon_production` while `DB_PASS` is empty because this setup will use the ident authentication method (system user "mastodon" maps to postgres user "mastodon").
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@ -180,7 +194,7 @@ User=mastodon
|
|||||||
WorkingDirectory=/home/mastodon/live
|
WorkingDirectory=/home/mastodon/live
|
||||||
Environment="RAILS_ENV=production"
|
Environment="RAILS_ENV=production"
|
||||||
Environment="DB_POOL=5"
|
Environment="DB_POOL=5"
|
||||||
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q push
|
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
|
||||||
TimeoutSec=15
|
TimeoutSec=15
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
@ -221,7 +235,7 @@ I recommend creating a couple cronjobs for the following tasks:
|
|||||||
|
|
||||||
You may want to run `which bundle` first and copypaste that full path instead of simply `bundle` in the above commands because cronjobs usually don't have all the paths set. The time and intervals of when to run these jobs are up to you, but once every day should be enough for all.
|
You may want to run `which bundle` first and copypaste that full path instead of simply `bundle` in the above commands because cronjobs usually don't have all the paths set. The time and intervals of when to run these jobs are up to you, but once every day should be enough for all.
|
||||||
|
|
||||||
You can edit the cronjob file for the `mastodon` user by running `sudo crontab -e mastodon` (outside of the mastodon user).
|
You can edit the cronjob file for the `mastodon` user by running `sudo crontab -e -u mastodon` (outside of the mastodon user).
|
||||||
|
|
||||||
## Things to look out for when upgrading Mastodon
|
## Things to look out for when upgrading Mastodon
|
||||||
|
|
||||||
|
13
docs/Running-Mastodon/Scalingo-guide.md
Normal file
13
docs/Running-Mastodon/Scalingo-guide.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Scalingo guide
|
||||||
|
==============
|
||||||
|
|
||||||
|
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
|
||||||
|
|
||||||
|
1. Click the above button.
|
||||||
|
2. Fill in the options requested.
|
||||||
|
* You can use a .scalingo.io domain, which will be simple to set up, or you can use a custom domain.
|
||||||
|
* You will want Amazon S3 for file storage. The only exception is for development purposes, where you may not care if files are not saved. Follow a guide online for creating a free Amazon S3 bucket and Access Key, then enter the details.
|
||||||
|
* If you want your Mastodon to be able to send emails, configure SMTP settings here (or later). Consider using [Mailgun](https://mailgun.com) or similar, who offer free plans that should suit your interests.
|
||||||
|
3. Deploy! The app should be set up, with a working web interface and database. You can change settings and manage versions from the Heroku dashboard.
|
||||||
|
|
||||||
|
You may need to use the `scalingo` CLI application to run `USERNAME=yourUsername rails mastodon:make_admin` to make yourself an admin.
|
@ -5,21 +5,48 @@ There is also a list at [instances.mastodon.xyz](https://instances.mastodon.xyz)
|
|||||||
|
|
||||||
| Name | Theme/Notes, if applicable | Open Registrations | IPv6 |
|
| Name | Theme/Notes, if applicable | Open Registrations | IPv6 |
|
||||||
| -------------|-------------|---|---|
|
| -------------|-------------|---|---|
|
||||||
| [mastodon.social](https://mastodon.social) |Flagship, quick updates|Yes|No|
|
| [mastodon.social](https://mastodon.social) |Flagship, quick updates|No|No|
|
||||||
|
| [securitymastod.one](https://securitymastod.one/) |Information security enthusiasts and pros|Yes|Yes|
|
||||||
|
| [mastodon.cx](https://mastodon.cx/) |Alternative Mastodon instance hosted in France|Yes|Yes|
|
||||||
|
| [mastodon.network](https://mastodon.network) |N/A|Yes|Yes|
|
||||||
| [awoo.space](https://awoo.space) |Intentionally moderated, only federates with mastodon.social|Yes|No|
|
| [awoo.space](https://awoo.space) |Intentionally moderated, only federates with mastodon.social|Yes|No|
|
||||||
| [social.tchncs.de](https://social.tchncs.de)|N/A|Yes|No|
|
|
||||||
| [animalliberation.social](https://animalliberation.social) |Animal Rights|Yes|No|
|
| [animalliberation.social](https://animalliberation.social) |Animal Rights|Yes|No|
|
||||||
| [socially.constructed.space](https://socially.constructed.space) |Single user|No|No|
|
| [socially.constructed.space](https://socially.constructed.space) |Single user|No|No|
|
||||||
| [epiktistes.com](https://epiktistes.com) |N/A|Yes|No|
|
| [epiktistes.com](https://epiktistes.com) |N/A|Yes|No|
|
||||||
| [gay.crime.team](https://gay.crime.team) |the place for doin' gay crime online (please don't actually do crime here)|Yes|No|
|
| [fern.surgeplay.com](https://fern.surgeplay.com) |Federates everywhere, Minecraft-focused|Yes|No
|
||||||
|
| [gay.crime.team](https://gay.crime.team) |the place for doin' gay crime online (please don't actually do crime here)|No|No|
|
||||||
| [icosahedron.website](https://icosahedron.website/) |Icosahedron-themed (well, visually), open registration.|Yes|No|
|
| [icosahedron.website](https://icosahedron.website/) |Icosahedron-themed (well, visually), open registration.|Yes|No|
|
||||||
| [memetastic.space](https://memetastic.space) |Memes|Yes|No|
|
| [memetastic.space](https://memetastic.space) |Memes|Yes|No|
|
||||||
| [social.diskseven.com](https://social.diskseven.com) |Single user|No|No (DNS entry but no response)|
|
| [social.diskseven.com](https://social.diskseven.com) |Single user|No|Yes|
|
||||||
| [social.gestaltzerfall.net](https://social.gestaltzerfall.net) |Single user|No|No|
|
| [social.gestaltzerfall.net](https://social.gestaltzerfall.net) |Single user|No|No|
|
||||||
| [mastodon.xyz](https://mastodon.xyz) |N/A|Yes|Yes|
|
| [mastodon.xyz](https://mastodon.xyz) |N/A|Yes|Yes|
|
||||||
| [social.targaryen.house](https://social.targaryen.house) |N/A|Yes|No|
|
| [mastodon.partipirate.org](https://mastodon.partipirate.org) |French Pirate Party Instance - Politics and stuff|Yes|No|
|
||||||
| [social.mashek.net](https://social.mashek.net) |Themed and customised for Mashekstein Labs community. Selectively federates.|Yes|No|
|
| [social.targaryen.house](https://social.targaryen.house) |Federates everywhere, quick updates.|Yes|Yes|
|
||||||
| [masto.themimitoof.fr](https://masto.themimitoof.fr) |N/A|Yes|Yes|
|
| [masto.themimitoof.fr](https://masto.themimitoof.fr) |N/A|Yes|Yes|
|
||||||
|
| [mstdn.io](https://mstdn.io) |N/A|Yes|Yes|
|
||||||
| [social.imirhil.fr](https://social.imirhil.fr) |N/A|No|Yes|
|
| [social.imirhil.fr](https://social.imirhil.fr) |N/A|No|Yes|
|
||||||
|
| [social.wxcafe.net](https://social.wxcafe.net) |Open registrations, queer people, activists, safe as much as possible |Yes|Yes|
|
||||||
|
| [octodon.social](https://octodon.social) |Open registrations, federates everywhere, cutest instance yet|Yes|Yes|
|
||||||
|
| [mastodon.club](https://mastodon.club)|Open Registration, Open Federation, Mostly Canadians|Yes|No|
|
||||||
|
| [mastodon.irish](https://mastodon.irish)|Open Registration|Yes|No|
|
||||||
|
| [hostux.social](https://hostux.social) |N/A|Yes|Yes|
|
||||||
|
| [social.alex73630.xyz](https://social.alex73630.xyz) |Francophones|Yes|Yes|
|
||||||
|
| [oc.todon.fr](https://oc.todon.fr) |Modérée et principalement francophone, pas de tolérances pour misogynie/LGBTphobies/validisme/etc.|Yes|Yes|
|
||||||
|
| [maly.io](https://maly.io) |N/A|Yes|No|
|
||||||
|
| [social.lou.lt](https://social.lou.lt) |N/A|Yes|No|
|
||||||
|
| [mastodon.ninetailed.uk](https://mastodon.ninetailed.uk) |N/A|Yes|No|
|
||||||
|
| [soc.louiz.org](https://soc.louiz.org) |"Coucou"|Yes|No|
|
||||||
|
| [7nw.eu](https://7nw.eu) |N/A|Yes|No|
|
||||||
|
| [mastodon.gougere.fr](https://mastodon.gougere.fr)|N/A|Yes|No|
|
||||||
|
| [aleph.land](https://aleph.land)|N/A|Yes|No|
|
||||||
|
| [share.elouworld.org](https://share.elouworld.org)|N/A|No|No|
|
||||||
|
| [social.lkw.tf](https://social.lkw.tf)|N/A|No|No|
|
||||||
|
| [manowar.social](https://manowar.social)|N/A|No|No|
|
||||||
|
| [social.ballpointcarrot.net](https://social.ballpointcarrot.net)|N/A|No|No|
|
||||||
|
| [social.nasqueron.org](https://social.nasqueron.org) |Dreamers, open source developers, free culture|Yes|Yes|
|
||||||
|
| [status.dissidence.ovh](https://status.dissidence.ovh)|N/A|Yes|Yes|
|
||||||
|
| [mastodon.cc](https://mastodon.cc)|Art|Yes|No|
|
||||||
|
| [mastodon.technology](https://mastodon.technology)|Open registrations, federates everywhere, for tech folks|Yes|No|
|
||||||
|
| [mastodon.systemlab.fr](https://mastodon.systemlab.fr/)|Le mastodon Français, informatique, jeux-vidéos, gaming et hébergement.|Yes|No|
|
||||||
|
|
||||||
Let me know if you start running one so I can add it to the list! (Alternatively, add it yourself as a pull request).
|
Let me know if you start running one so I can add it to the list! (Alternatively, add it yourself as a pull request).
|
||||||
|
@ -26,17 +26,17 @@ Mastodon User's Guide
|
|||||||
|
|
||||||
## Intro
|
## Intro
|
||||||
|
|
||||||
Mastodon is a social network application based on the GNU Social protocol. It behaves a lot like other social networks, especially Twitter, with one key difference - it is open-source and anyone can start their own server (also called an "instance"), and users of any instance can interact freely with those of other instances (called "federation"). Thus, it is possible for small communities to set up their own servers to use amongst themselves while also allowing interaction with other communities.
|
Mastodon is a social network application based on the GNU Social protocol. It behaves a lot like other social networks, especially Twitter, with one key difference - it is open-source and anyone can start their own server (also called an "*instance*"), and users of any instance can interact freely with those of other instances (called "*federation*"). Thus, it is possible for small communities to set up their own servers to use amongst themselves while also allowing interaction with other communities.
|
||||||
|
|
||||||
#### Decentralization and Federation
|
#### Decentralization and Federation
|
||||||
|
|
||||||
Mastodon is a system decentralized through a concept called "federation" - rather than depending on a single person or organization to run its infrastructure, anyone can download and run the software and run their own server. Federation means different Mastodon servers can interact with each other seamlessly, similar to e.g. e-mail.
|
Mastodon is a system decentralized through a concept called "*federation*" - rather than depending on a single person or organization to run its infrastructure, anyone can download and run the software and run their own server. Federation means different Mastodon servers can interact with each other seamlessly, similar to e.g. e-mail.
|
||||||
|
|
||||||
As such, anyone can download Mastodon and e.g. run it for a small community of people, but any user registered on that instance can follow and send and read posts from other Mastodon instances (as well as servers running other GNU Social-compatible services). This means that not only is users' data not inherently owned by a company with an interest in selling it to advertisers, but also that if any given server shuts down its users can set up a new one or migrate to another instance, rather than the entire service being lost.
|
As such, anyone can download Mastodon and e.g. run it for a small community of people, but any user registered on that instance can follow and send and read posts from other Mastodon instances (as well as servers running other GNU Social-compatible services). This means that not only is users' data not inherently owned by a company with an interest in selling it to advertisers, but also that if any given server shuts down its users can set up a new one or migrate to another instance, rather than the entire service being lost.
|
||||||
|
|
||||||
Within each Mastodon instance, usernames just appear as `@username`, similar to other services such as Twitter. Users from other instances appear, and can be searched for and followed, as `@user@servername.ext` - so e.g. `@gargron` on the `mastodon.social` instance can be followed from other instances as `@gargron@mastodon.social`).
|
Within each Mastodon instance, usernames just appear as `@username`, similar to other services such as Twitter. Users from other instances appear, and can be searched for and followed, as `@user@servername.ext` - so e.g. `@gargron` on the `mastodon.social` instance can be followed from other instances as `@gargron@mastodon.social`).
|
||||||
|
|
||||||
Posts from users on external instances are "federated" into the local one, i.e. if `user1@mastodon1` follows `user2@gnusocial2`, any posts `user2@gnusocial2` makes appear in both `user1@mastodon`'s Home feed and the public timeline on the `mastodon1` server. Mastodon server administrators have some control over this and can exclude users' posts from appearing on the public timeline; post privacy settings from users on Mastodon instances also affect this, see below in the [Toot Privacy](User-guide.md#toot-privacy) section.
|
Posts from users on external instances are "*federated*" into the local one, i.e. if `user1@mastodon1` follows `user2@gnusocial2`, any posts `user2@gnusocial2` makes appear in both `user1@mastodon`'s Home feed and the public timeline on the `mastodon1` server. Mastodon server administrators have some control over this and can exclude users' posts from appearing on the public timeline; post privacy settings from users on Mastodon instances also affect this, see below in the [Toot Privacy](User-guide.md#toot-privacy) section.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -160,13 +160,13 @@ Toot privacy is handled independently of account privacy, and individually for e
|
|||||||
|
|
||||||
**Unlisted** toots are toggled with the "Do not display in public timeline" option in the Compose pane. They are visible to anyone following you and appear on your profile page to the public even without a Mastodon login, but do *not* appear to anyone viewing the Public Timeline while logged into Mastodon.
|
**Unlisted** toots are toggled with the "Do not display in public timeline" option in the Compose pane. They are visible to anyone following you and appear on your profile page to the public even without a Mastodon login, but do *not* appear to anyone viewing the Public Timeline while logged into Mastodon.
|
||||||
|
|
||||||
**Private** toots, finally, are toggled with the "Mark as private" switch. Private toots do not appear in the public timeline nor on your profile page to anyone viewing it unless they are on your Followers list. This means the option is of very limited use if your account is not also set to be private (as anyone can follow you without confirmation and thus see your private toots). However the separation of this means that if you *do* set your entire account to private, you can switch this option off on a toot to make unlisted or even public toots from your otherwise private account.
|
**Private** toots, finally, are toggled with the "Mark as private" switch. Private toots do not appear in the public timeline nor on your profile page to anyone viewing it unless they are on your Followers list. This means the option is of very limited use if your account is not also set to be private (as anyone can follow you without confirmation and thus see your private toots). However the separation of this means that if you *do* set your entire account to private, you can switch this option off on a toot to make unlisted or even public toots from your otherwise private account. Private posts are not encrypted. Make sure you trust your instance admin not to just read your private posts on the back-end.
|
||||||
|
|
||||||
Private toots do not federate to other instances, unless you @mention a remote user. In this case, they will federate to their instance *and may appear there PUBLICLY*. A warning will be displayed if you're composing a private toot that will federate to another instance.
|
Private toots do not federate to other instances, unless you @mention a remote user. In this case, they will federate to their instance *and may appear there PUBLICLY*. A warning will be displayed if you're composing a private toot that will federate to another instance.
|
||||||
|
|
||||||
Private toots cannot be boosted. If someone you follow makes a private toot, it will appear in your timeline with a padlock icon in place of the Boost icon. **NOTE** that remote instances may not respect this.
|
Private toots cannot be boosted. If someone you follow makes a private toot, it will appear in your timeline with a padlock icon in place of the Boost icon. **NOTE** that remote instances may not respect this.
|
||||||
|
|
||||||
**Direct** messages are only visible to users you have @mentioned in them. This does *not* federate to protect your privacy (as other instances may ignore the "Direct" status and display the messages as public if they were to receive them), even if you have @mentioned a remote user.
|
**Direct** posts are only visible to users you have @mentioned in them and cannot be boosted. Like with private posts, you should be mindful that the remote instance may not respect this protocol. If you are discussing a sensitive matter you should move the conversation off of Mastodon.
|
||||||
|
|
||||||
To summarise:
|
To summarise:
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ Toot Privacy | Visible on Profile | Visible on Public Timeline | Federates to ot
|
|||||||
Public | Anyone incl. anonymous viewers | Yes | Yes
|
Public | Anyone incl. anonymous viewers | Yes | Yes
|
||||||
Unlisted | Anyone incl. anonymous viewers | No | Yes
|
Unlisted | Anyone incl. anonymous viewers | No | Yes
|
||||||
Private | Followers only | No | Only remote @mentions
|
Private | Followers only | No | Only remote @mentions
|
||||||
Direct | No | No | No
|
Direct | No | No | Only remote @mentions
|
||||||
|
|
||||||
#### Blocking
|
#### Blocking
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ Returns a [Status](#status).
|
|||||||
|
|
||||||
#### Getting status context:
|
#### Getting status context:
|
||||||
|
|
||||||
GET /api/v1/statuses/:id/contexts
|
GET /api/v1/statuses/:id/context
|
||||||
|
|
||||||
Returns a [Context](#context).
|
Returns a [Context](#context).
|
||||||
|
|
||||||
|
87
scalingo.json
Normal file
87
scalingo.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"name": "Mastodon",
|
||||||
|
"description": "A GNU Social-compatible microblogging server",
|
||||||
|
"repository": "https://github.com/johnsudaar/mastodon",
|
||||||
|
"logo": "https://github.com/tootsuite/mastodon/raw/master/app/assets/images/logo.png",
|
||||||
|
"env": {
|
||||||
|
"LOCAL_DOMAIN": {
|
||||||
|
"description": "The domain that your Mastodon instance will run on (this can be appname.scalingo.io or a custom domain)",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"LOCAL_HTTPS": {
|
||||||
|
"description": "Will your domain support HTTPS? (Automatic for *.scalingo.io, requires manual configuration for custom domains)",
|
||||||
|
"value": "true",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"PAPERCLIP_SECRET": {
|
||||||
|
"description": "The secret key for storing media files",
|
||||||
|
"generator": "secret"
|
||||||
|
},
|
||||||
|
"SECRET_KEY_BASE": {
|
||||||
|
"description": "The secret key base",
|
||||||
|
"generator": "secret"
|
||||||
|
},
|
||||||
|
"SINGLE_USER_MODE": {
|
||||||
|
"description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
|
||||||
|
"value": "false",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"S3_ENABLED": {
|
||||||
|
"description": "Should Mastodon use Amazon S3 for storage? This is highly recommended, as Scalingo does not have persistent file storage (files will be lost).",
|
||||||
|
"value": "true",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"S3_BUCKET": {
|
||||||
|
"description": "Amazon S3 Bucket",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"S3_REGION": {
|
||||||
|
"description": "Amazon S3 region that the bucket is located in",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"AWS_ACCESS_KEY_ID": {
|
||||||
|
"description": "Amazon S3 Access Key",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"AWS_SECRET_ACCESS_KEY": {
|
||||||
|
"description": "Amazon S3 Secret Key",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_SERVER": {
|
||||||
|
"description": "Hostname for SMTP server, if you want to enable email",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_PORT": {
|
||||||
|
"description": "Port for SMTP server",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_LOGIN": {
|
||||||
|
"description": "Username for SMTP server",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_PASSWORD": {
|
||||||
|
"description": "Password for SMTP server",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_DOMAIN": {
|
||||||
|
"description": "Domain for SMTP server. Will default to instance domain if blank.",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"SMTP_FROM_ADDRESS": {
|
||||||
|
"description": "Address to send emails from",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"BUILDPACK_URL": {
|
||||||
|
"description": "Internal scalingo configuration",
|
||||||
|
"required": true,
|
||||||
|
"value": "https://github.com/Scalingo/multi-buildpack.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
|
||||||
|
},
|
||||||
|
"addons": [
|
||||||
|
"scalingo-postgresql",
|
||||||
|
"scalingo-redis"
|
||||||
|
]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user