Merge branch 'master' into patch-1

This commit is contained in:
Effy Elden 2017-04-08 21:58:35 +10:00 committed by GitHub
commit 03fe20acf0
41 changed files with 247 additions and 85 deletions

View File

@ -17,7 +17,7 @@ Click on the screenshot to watch a demo of the UI:
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
Focus of the project on a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
The project focus is a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`

View File

@ -26,6 +26,10 @@
"description": "The secret key base",
"generator": "secret"
},
"OTP_SECRET": {
"description": "One-time password secret",
"generator": "secret"
},
"SINGLE_USER_MODE": {
"description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
"value": "false",

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
<path d="M527.194 543.7a28.362 28.362 0 0 0-56.723 0 25.73 25.73 0 0 0 2.67 11.674 26.42 26.42 0 0 0 5.672 8.34 28.2 28.2 0 0 0 40.04 0 31.87 31.87 0 0 0 6.006-8.34 28.8 28.8 0 0 0 2.336-11.674m-48.382-113.413a28.308 28.308 0 1 0 40.04 40.027 37.2 37.2 0 0 0 4.67-5.67 28.092 28.092 0 0 0 3.67-14.343 27.29 27.29 0 0 0-8.34-20.012 28.24 28.24 0 0 0-5.006-4 26.958 26.958 0 0 0-15.015-4.336 27.31 27.31 0 0 0-20.02 8.34m20.02-101.735a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34M231.9 573.717a28.18 28.18 0 1 0 8.342 20.012 27.308 27.308 0 0 0-8.342-20.014m-40.04-93.4a28.352 28.352 0 0 0 20.02 48.366 26.958 26.958 0 0 0 15.015-4.336 28.255 28.255 0 0 0 5.005-4 27.29 27.29 0 0 0 8.342-20.013 28.09 28.09 0 0 0-3.67-14.343 37.21 37.21 0 0 0-4.67-5.67 28.2 28.2 0 0 0-40.04 0m40.04-93.4a28.2 28.2 0 0 0-40.04 0 26.425 26.425 0 0 0-5.673 8.34 25.73 25.73 0 0 0-2.67 11.673 28.315 28.315 0 0 0 48.38 20.018 27.29 27.29 0 0 0 8.342-20.012 28.8 28.8 0 0 0-2.336-11.674 31.87 31.87 0 0 0-6.006-8.34m550.55 178.453a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34m20.02-85.057a28.2 28.2 0 0 0-40.04 0 37.2 37.2 0 0 0-4.672 5.67 28.092 28.092 0 0 0-3.67 14.343 27.29 27.29 0 0 0 8.342 20.013 28.248 28.248 0 0 0 5.005 4 26.96 26.96 0 0 0 15.015 4.336 28.3 28.3 0 0 0 20.02-48.366m-46.046-85.057a28.8 28.8 0 0 0-2.336 11.673 28.362 28.362 0 0 0 56.723 0 25.73 25.73 0 0 0-2.668-11.674 26.427 26.427 0 0 0-5.672-8.34 28.2 28.2 0 0 0-40.04 0 31.86 31.86 0 0 0-6.007 8.343z" fill="#2b90d9"/>
<path d="M853.52 146.764Q707.04 0 499.833 0 292.96 0 146.48 146.764 0 293.2 0 500q0 207.138 146.48 353.57T499.833 1000q207.207 0 353.687-146.43T1000 500q0-206.8-146.48-353.236zM213.547 708.806h-3.337q-43.043 0-73.407-30.02-30.03-30.354-30.03-73.382V395.93v-.666q1.335-41.027 30.03-69.713 30.364-30.35 73.407-30.35t73.073 30.354q29.363 29.02 30.364 70.38V615.41q2.336 55.037 46.713 93.4zM600.6 554.7q-1 41.36-30.364 70.38-30.03 30.353-73.073 30.354t-73.407-30.354q-28.7-28.686-30.03-69.713V345.23q0-43.03 30.03-73.382 30.364-30.02 73.407-30.02h150.15q-44.378 38.36-46.713 93.4zm286.954 50.7q0 43.03-30.03 73.382-30.364 30.02-73.407 30.02h-150.15q44.378-38.36 46.713-93.4v-219.47q1-41.362 30.364-70.38 30.03-30.355 73.073-30.355t73.407 30.354q28.7 28.687 30.03 69.714V605.4z" fill="#2b90d9"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -15,6 +15,7 @@ const ColumnCollapsable = React.createClass({
propTypes: {
icon: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
fullHeight: React.PropTypes.number.isRequired,
children: React.PropTypes.node,
onCollapse: React.PropTypes.func
@ -39,13 +40,13 @@ const ColumnCollapsable = React.createClass({
},
render () {
const { icon, fullHeight, children } = this.props;
const { icon, title, fullHeight, children } = this.props;
const { collapsed } = this.state;
const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable';
return (
<div style={{ position: 'relative' }}>
<div style={{...iconStyle }} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
<div title={`${title}`} style={{...iconStyle }} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
<Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
{({ opacity, height }) =>

View File

@ -6,7 +6,8 @@ import SettingToggle from '../../notifications/components/setting_toggle';
import SettingText from './setting_text';
const messages = defineMessages({
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
settings: { id: 'home.settings', defaultMessage: 'Column settings' }
});
const outerStyle = {
@ -39,7 +40,7 @@ const ColumnSettings = React.createClass({
const { settings, onChange, onSave, intl } = this.props;
return (
<ColumnCollapsable icon='sliders' fullHeight={209} onCollapse={onSave}>
<ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={209} onCollapse={onSave}>
<div className='column-settings--outer' style={outerStyle}>
<span className='column-settings--section' style={sectionStyle}><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>

View File

@ -1,3 +1,9 @@
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
});
const iconStyle = {
fontSize: '16px',
padding: '15px',
@ -8,14 +14,22 @@ const iconStyle = {
zIndex: '2'
};
const ClearColumnButton = ({ onClick }) => (
<div className='column-icon' tabindex='0' style={iconStyle} onClick={onClick}>
<i className='fa fa-trash' />
</div>
);
const ClearColumnButton = React.createClass({
ClearColumnButton.propTypes = {
onClick: React.PropTypes.func.isRequired
};
propTypes: {
onClick: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
export default ClearColumnButton;
render () {
const { intl } = this.props;
return (
<div title={intl.formatMessage(messages.clear)} className='column-icon' tabIndex='0' style={iconStyle} onClick={this.onClick}>
<i className='fa fa-eraser' />
</div>
);
}
})
export default injectIntl(ClearColumnButton);

View File

@ -1,9 +1,13 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import SettingToggle from './setting_toggle';
const messages = defineMessages({
settings: { id: 'notifications.settings', defaultMessage: 'Column settings' }
});
const outerStyle = {
padding: '15px'
};
@ -30,14 +34,14 @@ const ColumnSettings = React.createClass({
mixins: [PureRenderMixin],
render () {
const { settings, onChange, onSave } = this.props;
const { settings, intl, onChange, onSave } = this.props;
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
return (
<ColumnCollapsable icon='sliders' fullHeight={616} onCollapse={onSave}>
<ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={616} onCollapse={onSave}>
<div className='column-settings--outer' style={outerStyle}>
<span className='column-settings--section' style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
@ -77,4 +81,4 @@ const ColumnSettings = React.createClass({
});
export default ColumnSettings;
export default injectIntl(ColumnSettings);

View File

@ -1,15 +1,15 @@
const en = {
"column_back_button.label": "Zurück",
"lightbox.close": "Schließen",
"loading_indicator.label": "Lade...",
"loading_indicator.label": "Lade",
"status.mention": "Erwähnen",
"status.delete": "Löschen",
"status.reply": "Antworten",
"status.reblog": "Teilen",
"status.favourite": "Favorisieren",
"status.reblogged_by": "{name} teilte",
"status.sensitive_warning": "Sensible Inhalte",
"status.sensitive_toggle": "Klicken um zu zeigen",
"status.sensitive_warning": "Heikle Inhalte",
"status.sensitive_toggle": "Klicke, um sie zu sehen",
"status.open": "Öffnen",
"video_player.toggle_sound": "Ton umschalten",
"account.mention": "Erwähnen",
@ -20,17 +20,17 @@ const en = {
"account.follow": "Folgen",
"account.posts": "Beiträge",
"account.follows": "Folgt",
"account.followers": "Folger",
"account.followers": "Folgende",
"account.follows_you": "Folgt dir",
"account.requested": "Warte auf Erlaubnis",
"getting_started.heading": "Erste Schritte",
"getting_started.about_addressing": "Du kannst Leuten folgen, falls du ihren Nutzernamen und ihre Domain kennst, in dem du eine e-mail-artige Addresse in das Suchfeld oben an der Seite eingibst.",
"getting_started.about_shortcuts": "Falls der Zielnutzer an derselben Domain ist wie du, funktioniert der Nutzername auch alleine. Das gilt auch für Erwähnungen in Beiträgen.",
"getting_started.about_addressing": "Du kannst Leuten folgen, falls du ihren Nutzernamen und ihre Domain kennst, in dem du eine e-mail-artige Addresse in das Suchfeld oben auf der Seite eingibst.",
"getting_started.about_shortcuts": "Falls die Person auf derselben Domain ist wie du, reicht auch ihr Nutzername alleine. Das gilt auch für Erwähnungen in Beiträgen.",
"getting_started.about_developer": "Der Entwickler des Projekts kann unter Gargron@mastodon.social gefunden werden",
"getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
"column.home": "Home",
"column.mentions": "Erwähnungen",
"column.public": "Gesamtes Bekanntes Netz",
"column.public": "Gesamtes bekanntes Netz",
"column.notifications": "Mitteilungen",
"column.follow_requests": "Folgeanfragen",
"tabs_bar.compose": "Schreiben",
@ -38,11 +38,11 @@ const en = {
"tabs_bar.mentions": "Erwähnungen",
"tabs_bar.public": "Gesamtes Netz",
"tabs_bar.notifications": "Mitteilungen",
"compose_form.placeholder": "Worüber möchstest du schreiben?",
"compose_form.placeholder": "Worüber möchtest du schreiben?",
"compose_form.publish": "Tröt",
"compose_form.sensitive": "Medien als sensitiv markieren",
"compose_form.unlisted": "Öffentlich nicht auflisten",
"compose_form.sensitive": "Medien als heikel markieren",
"compose_form.private": "Als privat markieren",
"compose_form.unlisted": "Nicht öffentlich auflisten",
"navigation_bar.edit_profile": "Profil bearbeiten",
"navigation_bar.preferences": "Einstellungen",
"navigation_bar.public_timeline": "Öffentlich",
@ -52,15 +52,15 @@ const en = {
"search.placeholder": "Suche",
"search.account": "Konto",
"search.hashtag": "Hashtag",
"upload_button.label": "Media-Datei anfügen",
"upload_button.label": "Mediendatei hinzufügen",
"upload_form.undo": "Entfernen",
"notification.follow": "{name} folgt dir",
"notification.favourite": "{name} favorisierte deinen Status",
"notification.reblog": "{name} teilte deinen Status",
"notification.mention": "{name} erwähnte dich",
"notifications.column_settings.alert": "Desktop-Benachrichtigunen",
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
"notifications.column_settings.show": "In der Spalte anzeigen",
"notifications.column_settings.follow": "Neue Folger:",
"notifications.column_settings.follow": "Neue Folgende:",
"notifications.column_settings.favourite": "Favorisierungen:",
"notifications.column_settings.mention": "Erwähnungen:",
"notifications.column_settings.reblog": "Geteilte Beiträge:",

View File

@ -10,6 +10,10 @@ const en = {
"status.reblogged_by": "{name} boosted",
"status.sensitive_warning": "Sensitive content",
"status.sensitive_toggle": "Click to view",
"status.show_more": "Show more",
"status.show_less": "Show less",
"status.open": "Expand this status",
"status.report": "Report @{name}",
"video_player.toggle_sound": "Toggle sound",
"account.mention": "Mention @{name}",
"account.edit_profile": "Edit profile",

View File

@ -10,6 +10,10 @@ const fr = {
"status.reblogged_by": "{name} a partagé :",
"status.sensitive_warning": "Contenu délicat",
"status.sensitive_toggle": "Cliquer pour dévoiler",
"status.show_more": "Déplier",
"status.show_less": "Replier",
"status.open": "Déplier ce status",
"status.report": "Signaler @{name}",
"video_player.toggle_sound": "Mettre/Couper le son",
"account.mention": "Mentionner",
"account.edit_profile": "Modifier le profil",
@ -35,7 +39,6 @@ const fr = {
"column.community": "Fil public local",
"column.public": "Fil public global",
"column.notifications": "Notifications",
"column.public": "Fil public",
"column.blocks": "Utilisateurs bloqués",
"column.favourites": "Favoris",
"tabs_bar.compose": "Composer",
@ -44,9 +47,9 @@ const fr = {
"tabs_bar.public": "Fil public global",
"tabs_bar.notifications": "Notifications",
"compose_form.placeholder": "Quavez-vous en tête ?",
"compose_form.publish": "Pouet ",
"compose_form.publish": "Pouet",
"compose_form.sensitive": "Marquer le média comme délicat",
"compose_form.spoiler": "Masquer le texte par un avertissement",
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
"compose_form.private": "Rendre privé",
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodons. Si {domains} {domainsCount, plural, one {n'est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n'y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d'une autre manière à d'autres personnes imprévues",
"compose_form.unlisted": "Ne pas afficher dans les fils publics",
@ -58,7 +61,6 @@ const fr = {
"navigation_bar.blocks": "Utilisateurs bloqués",
"navigation_bar.favourites": "Favoris",
"navigation_bar.info": "Plus d'informations",
"notification.favourite": "{name} a ajouté à ses favoris :",
"navigation_bar.logout": "Déconnexion",
"reply_indicator.cancel": "Annuler",
"search.placeholder": "Chercher",

View File

@ -1,14 +1,13 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
include Localized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'"
include Localized
helper_method :current_account
rescue_from ActionController::RoutingError, with: :not_found
@ -41,7 +40,6 @@ class ApplicationController < ActionController::Base
# 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
def check_suspension

View File

@ -4,13 +4,25 @@ module Localized
extend ActiveSupport::Concern
included do
before_action :set_locale
around_action :set_locale
end
private
def set_locale
I18n.locale = current_user.try(:locale) || default_locale
rescue I18n::InvalidLocale
I18n.locale = default_locale
locale = default_locale
if user_signed_in?
begin
locale = current_user.try(:locale) || default_locale
rescue I18n::InvalidLocale
locale = default_locale
end
end
I18n.with_locale(locale) do
yield
end
end
def default_locale

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include Localized
skip_before_action :authenticate_resource_owner!
before_action :store_current_location
before_action :authenticate_resource_owner!
include Localized
private
def store_current_location

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
include Localized
skip_before_action :authenticate_resource_owner!
before_action :store_current_location
before_action :authenticate_resource_owner!
include Localized
private
def store_current_location

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module SiteTitleHelper
def site_title
Setting.site_title.to_s
end
end

View File

@ -20,8 +20,6 @@ class FollowRemoteAccountService < BaseService
Rails.logger.debug "Looking up webfinger for #{uri}"
account = Account.new(username: username, domain: domain)
data = Goldfinger.finger("acct:#{uri}")
raise Goldfinger::Error, 'Missing resource links' if data.link('http://schemas.google.com/g/2010#updates-from').nil? || data.link('salmon').nil? || data.link('http://webfinger.net/rel/profile-page').nil? || data.link('magic-public-key').nil?
@ -37,6 +35,7 @@ class FollowRemoteAccountService < BaseService
domain_block = DomainBlock.find_by(domain: domain)
account = Account.new(username: confirmed_username, domain: confirmed_domain)
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
account.salmon_url = data.link('salmon').href
account.url = data.link('http://webfinger.net/rel/profile-page').href
@ -51,8 +50,8 @@ class FollowRemoteAccountService < BaseService
account.uri = get_account_uri(xml)
account.hub_url = hubs.first.attribute('href').value
get_profile(body, account)
account.save!
get_profile(body, account)
account
end

View File

@ -5,14 +5,13 @@ class ProcessFeedService < BaseService
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
update_author(body, xml, account)
update_author(body, account)
process_entries(xml, account)
end
private
def update_author(body, xml, account)
return if xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS).nil?
def update_author(body, account)
RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
end

View File

@ -1,7 +1,12 @@
# frozen_string_literal: true
class UpdateRemoteProfileService < BaseService
def call(xml, account, resubscribe = false)
def call(body, account, resubscribe = false)
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
xml = xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS) || xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)
return if xml.nil?
author_xml = xml.at_xpath('./xmlns:author', xmlns: TagManager::XMLNS) || xml.at_xpath('./dfrn:owner', dfrn: TagManager::DFRN_XMLNS)
@ -12,9 +17,9 @@ class UpdateRemoteProfileService < BaseService
account.note = author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).nil?
account.locked = author_xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content == 'private'
unless account.suspended? || DomainBlock.find_by(domain: account.domain)&.reject_media?
account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank?
account.header_remote_url = author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'].blank?
if !account.suspended? && !DomainBlock.find_by(domain: account.domain)&.reject_media?
account.avatar_remote_url = link_href_from_xml(author_xml, 'avatar') if link_has_href?(author_xml, 'avatar')
account.header_remote_url = link_href_from_xml(author_xml, 'header') if link_has_href?(author_xml, 'header')
end
end
@ -25,4 +30,14 @@ class UpdateRemoteProfileService < BaseService
SubscribeService.new.call(account) if resubscribe && (account.hub_url != old_hub_url)
end
private
def link_href_from_xml(xml, type)
xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS)['href']
end
def link_has_href?(xml, type)
!(xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS).nil? || xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS)['href'].blank?)
end
end

View File

@ -5,7 +5,7 @@
= Rails.configuration.x.local_domain
- content_for :header_tags do
%meta{ property: 'og:site_name', content: 'Mastodon' }/
%meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'website' }/
%meta{ property: 'og:title', content: Rails.configuration.x.local_domain }/
%meta{ property: 'og:description', content: @description.blank? ? "Mastodon is a free, open-source social network server. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly" : strip_tags(@description) }/

View File

@ -5,7 +5,7 @@
%link{ rel: 'salmon', href: api_salmon_url(@account.id) }/
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/
%meta{ property: 'og:site_name', content: 'Mastodon' }/
%meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'profile' }/
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
%meta{ property: 'og:description', content: @account.note }/

View File

@ -15,7 +15,7 @@
- if content_for?(:page_title)
= yield(:page_title)
= ' - '
= Setting.site_title
= site_title
= stylesheet_link_tag 'application', media: 'all'
= csrf_meta_tags

View File

@ -2,7 +2,7 @@
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/
%link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/
%meta{ property: 'og:site_name', content: 'Mastodon' }/
%meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'article' }/
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/

View File

@ -0,0 +1,5 @@
<p>Bienvenue <%= @resource.email %>&nbsp;!</p>
<p>Vous pouvez confirmer l'email de votre compte Mastodon en cliquant sur le lien ci-dessous&nbsp;:</p>
<p><%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %></p>

View File

@ -0,0 +1,5 @@
Bienvenue <%= @resource.email %> !
Vous pouvez confirmer l'email de votre compte Mastodon en cliquant sur le lien ci-dessous :
<%= confirmation_url(@resource, confirmation_token: @token) %>

View File

@ -0,0 +1,3 @@
<p>Bonjour <%= @resource.email %>&nbsp;!</p>
<p>Nous vous contactons pour vous informer que votre mot de passe sur Mastodon a bien été modifié.</p>

View File

@ -0,0 +1,3 @@
Bonjour <%= @resource.email %> !
Nous vous contactons pour vous informer que votre mot de passe sur Mastodon a bien été modifié.

View File

@ -0,0 +1,8 @@
<p>Bonjour <%= @resource.email %>&nbsp;!</p>
<p>Quelqu'un a demandé à réinitialiser votre mot de passe sur Mastodon. Vous pouvez effectuer la réinitialisation en cliquant sur le lien ci-dessous.</p>
<p><%= link_to 'Modifier mon mot de passe', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce message.</p>
<p>Votre mot de passe ne sera pas modifié tant que vous n'accéderez pas au lien ci-dessus et n'en choisirez pas un nouveau.</p>

View File

@ -0,0 +1,8 @@
Bonjour <%= @resource.email %> !
Quelqu'un a demandé à réinitialiser votre mot de passe sur Mastodon. Vous pouvez effectuer la réinitialisation en cliquant sur le lien ci-dessous.
<%= edit_password_url(@resource, reset_password_token: @token) %>
Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce message.
Votre mot de passe ne sera pas modifié tant que vous n'accéderez pas au lien ci-dessus et n'en choisirez pas un nouveau.

View File

@ -6,14 +6,7 @@ class RemoteProfileUpdateWorker
sidekiq_options queue: 'pull'
def perform(account_id, body, resubscribe)
account = Account.find(account_id)
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
author_container = xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS) || xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)
UpdateRemoteProfileService.new.call(author_container, account, resubscribe)
UpdateRemoteProfileService.new.call(body, Account.find(account_id), resubscribe)
rescue ActiveRecord::RecordNotFound
true
end

View File

@ -7,8 +7,8 @@ de:
terms: AGB
accounts:
follow: Folgen
followers: Follower
following: Gefolgt
followers: Folger
following: Folgt
nothing_here: Hier gibt es nichts!
people_followed_by: Nutzer, denen %{name} folgt
people_who_follow: Nutzer, die %{name} folgen

View File

@ -7,7 +7,7 @@ fi:
business_email: 'Business e-mail:'
contact: Ota yhteyttä
description_headline: Mikä on %{domain}?
domain_count_after: muut palvelimet
domain_count_after: muuhun palvelimeen
domain_count_before: Yhdistyneenä
features:
api: Avoin API ohjelmille ja palveluille

View File

@ -5,13 +5,14 @@ fr:
about_this: À propos de cette instance
apps: Applications
business_email: E-mail professionnel
closed_registrations: Les inscriptions sont actuellement fermées sur cette instance. .
closed_registrations: Les inscriptions sont actuellement fermées sur cette instance.
contact: Contact
description_headline: Qu'est-ce que %{domain} ?
domain_count_after: autres instances
domain_count_before: Connectés à
features:
api: API ouverte aux apps et services
blocking: Outils complets de bloquage et masquage
blocks: Outils complets de bloquage et masquage
characters: 500 caractères par post
chronology: Fil chronologique
ethics: 'Pas de pubs, pas de pistage'
@ -21,6 +22,7 @@ fr:
features_headline: Ce qui rend Mastodon différent
get_started: Rejoindre le réseau
links: Liens
other_instances: Autres instances
source_code: Code source
status_count_after: posts
status_count_before: Ayant publié
@ -54,9 +56,24 @@ fr:
reset_password: Réinitialiser le mot de passe
set_new_password: Définir le nouveau mot de passe
authorize_follow:
error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant
follow: Suivre
prompt_html: 'Vous (<strong>%{self}</strong>) avez demandé à suivre:'
title: Suivre %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}h"
about_x_months: "%{count}mo"
about_x_years: "%{count}y"
almost_x_years: "%{count}y"
half_a_minute: A l'instant
less_than_x_minutes: "%{count}m"
less_than_x_seconds: A l'instant
over_x_years: "%{count}y"
x_days: "%{count}d"
x_minutes: "%{count}m"
x_months: "%{count}mo"
x_seconds: "%{count}s"
exports:
blocks: Vous bloquez
csv: CSV
@ -93,6 +110,9 @@ fr:
follow:
body: "%{name} vous suit !"
subject: "%{name} vous suit"
follow_request:
body: "%{name} a demandé à vous suivre"
subject: 'Abonné⋅es en attente : %{name}'
mention:
body: "%{name} vous a mentionné⋅e dans :"
subject: "%{name} vous a mentionné⋅e"
@ -132,7 +152,7 @@ fr:
formats:
default: '%d %b %Y, %H:%M'
two_factor_auth:
description_html: Si vous activez <strong>l'identification à deux facteurs</strong> vous devrez être en posession de votre téléphone afin de générer un code de connexion.
description_html: Si vous activez <strong>l'identification à deux facteurs</strong>, vous devrez être en possession de votre téléphone afin de générer un code de connexion.
disable: Désactiver
enable: Activer
instructions_html: "<strong>Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone</strong>. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."

View File

@ -3,7 +3,7 @@ de:
simple_form:
hints:
defaults:
locked: Erlaubt dir, Folger zu überprüfen, bevor sie dir folgen können
locked: Erlaubt dir, Nutzer zu überprüfen, bevor sie dir folgen können
labels:
defaults:
avatar: Avatar
@ -11,16 +11,16 @@ de:
confirm_password: Passwort bestätigen
current_password: Derzeitiges Passwort
display_name: Anzeigename
email: E-mail-Addresse
email: E-Mail-Addresse
header: Kopfbild
locale: Sprache
locked: Gesperrter Profil
locked: Gesperrtes Profil
new_password: Neues Passwort
note: Über mich
password: Passwort
username: Nutzername
interactions:
must_be_follower: Benachrichtigungen von nicht-Folgern blockieren
must_be_follower: Benachrichtigungen von Nicht-Folgern blockieren
must_be_following: Benachrichtigungen von Nutzern blockieren, denen ich nicht folge
notification_emails:
favourite: E-mail senden, wenn jemand meinen Beitrag favorisiert

View File

@ -33,7 +33,7 @@ fr:
must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas
must_be_following: Masquer les notifications des personnes que vous ne suivez pas
notification_emails:
digest: Envoyer des emails récapitulatifs
digest: Envoyer des courriels récapitulatifs
favourite: Envoyer un courriel lorsque quelquun ajoute mes statuts à ses favoris
follow: Envoyer un courriel lorsque quelquun me suit
follow_request: Envoyer un courriel lorsque quelqu'un demande à me suivre

View File

@ -10,7 +10,7 @@ These people make the development of Mastodon possible through [Patreon](https:/
- [Kurtis Rainbolt-Greene](https://mastodon.social/users/krainboltgreene)
- [Kit Redgrave](https://socially.constructed.space/users/KitRedgrave)
- [Zeipher](https://mastodon.social/users/Zeipher)
- [Effy Elden](https://toot.zone/users/effy)
- [Effy Elden](https://mastodon.social/users/effy)
- [Zoë Quinn](https://mastodon.social/users/zoequinn)
**Thank you to the following people**

View File

@ -39,6 +39,40 @@ 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 deploy from the web, the format for all the S3 bits use Paperclip conventions:
S3 Bucket is just the name of the bucket, e.g. `bucketname` not the full ARN.
S3 Region is the AWS code for the region e.g. `ap-northeast-1` not the name of the city displayed on the AWS Dashboard.
To protect the privacy of the users of the your instance, you should have permissons on the your S3 bucket set to no-read and no-write for the public and non-application-specific AWS users, with only one authorized IAM user or group set up to be able to upload or display content. This is an example of an IAM policy used for the S3 bucket used Mastadon instance hentai.loan:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::hentailoan”,
"arn:aws:s3:::hentailoan/*"
]
}
]
}
## Deployment
You can deploy from the Heroku web interface or from the command line. Run:

View File

@ -90,7 +90,9 @@ It is recommended to create a special user for mastodon on the server (you could
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 -
apt-get install nodejs
sudo apt-get install nodejs
sudo npm install -g yarn
## Redis

View File

@ -7,7 +7,7 @@ There is also a list at [instances.mastodon.xyz](https://instances.mastodon.xyz)
| -------------|-------------|---|---|
| [mastodon.social](https://mastodon.social) |Flagship, quick updates|No|No|
| [securitymastod.one](https://securitymastod.one/) |Information security enthusiasts and pros|Yes|Yes|
| [mastodon.nuzgo.net](https://mastodon.nuzgo.net/) |Mastodon instance hosted in Paris |Yes|No|
| [mastodon.nuzgo.net](https://mastodon.nuzgo.net/) |Mastodon instance hosted in Paris |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|
@ -69,7 +69,7 @@ There is also a list at [instances.mastodon.xyz](https://instances.mastodon.xyz)
| [meow.social](https://meow.social)|A furry fandom focused instance|Yes|No|
| [neumastodon.com](https://neumastodon.com/)|Northeastern University Mastodon |Yes|No|
| [dancingbanana.party](https://dancingbanana.party)|La banane qui danse.|Yes|No|
| [mastodon.brussels.fr](https://mastodon.brussels/)|Le mastodon pour les belges, si vous aimez la bonne ambiance venez nous rejoindre !|Yes|Yes|
| [mastodon.brussels](https://mastodon.brussels/)|Le mastodon pour les belges, si vous aimez la bonne ambiance venez nous rejoindre !|Yes|Yes|
| [mastodon.llamasweet.tech](https://mastodon.llamasweet.tech/)|Mastodon about Android developement|Yes|No|
| [manx.social](https://manx.social/)|Instance for the Isle of Man|Yes|Yes|
| [mastodon.host](https://mastodon.host/)|Lightly moderated, federates everywhere and has a follow bot ( Huge federated timeline )|Yes|No|
@ -77,6 +77,7 @@ There is also a list at [instances.mastodon.xyz](https://instances.mastodon.xyz)
| [oulipo.social](https://oulipo.social/)|An Oulipo Mastodon in which that fifth symbol in Latin script is taboo|Yes|No|
| [indigo.zone](https://indigo.zone)|Open Registrations, General Purpose|Yes|No|
| [mastodones.club](https://mastodones.club)|Mastodon en español|Yes|Yes|
| [mst3k.interlinked.me](https://mst3k.interlinked.me)|Open registrations, general purpose|Yes|Yes|
Let me know if you start running one so I can add it to the list! (Alternatively, add it yourself as a pull request).
We are no longer maintaining this list as instances are popping up too quickly for using GitHub to be a tenable system for tracking them. Please standby while we work on another solution

View File

@ -43,6 +43,7 @@ ___
- [For Python](https://github.com/halcy/Mastodon.py)
- [For JavaScript](https://github.com/Zatnosk/libodonjs)
- [For JavaScript (Node.js)](https://github.com/jessicahayley/node-mastodon)
- [For Elixir](https://github.com/milmazz/hunter)
___

View File

@ -0,0 +1,15 @@
require "rails_helper"
describe "site_title" do
it "Uses the Setting.site_title value when it exists" do
Setting.site_title = "New site title"
expect(helper.site_title).to eq "New site title"
end
it "returns empty string when Setting.site_title is nil" do
Setting.site_title = nil
expect(helper.site_title).to eq ""
end
end

View File

@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe UpdateRemoteProfileService do
let(:xml) { Nokogiri::XML(File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom'))).at_xpath('//xmlns:feed') }
let(:xml) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom')) }
subject { UpdateRemoteProfileService.new }