Compare commits

..

2 Commits

31 changed files with 66 additions and 205 deletions

View File

@@ -44,10 +44,7 @@ const Button = React.createClass({
cursor: 'pointer',
lineHeight: `${this.props.size}px`,
borderRadius: '4px',
textDecoration: 'none',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden'
textDecoration: 'none'
};
return (

View File

@@ -48,7 +48,6 @@ import es from 'react-intl/locale-data/es';
import fi from 'react-intl/locale-data/fi';
import fr from 'react-intl/locale-data/fr';
import hu from 'react-intl/locale-data/hu';
import it from 'react-intl/locale-data/it';
import ja from 'react-intl/locale-data/ja';
import pt from 'react-intl/locale-data/pt';
import nl from 'react-intl/locale-data/nl';
@@ -79,7 +78,6 @@ addLocaleData([
...fi,
...fr,
...hu,
...it,
...ja,
...pt,
...pt_br,

View File

@@ -197,9 +197,9 @@ const ComposeForm = React.createClass({
<SpoilerButtonContainer />
</div>
<div style={{ display: 'flex', minWidth: 0 }}>
<div style={{ display: 'flex' }}>
<div style={{ paddingTop: '10px', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={text} /></div>
<div style={{ paddingTop: '10px', overflow: 'hidden' }}><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500} block /></div>
<div style={{ paddingTop: '10px' }}><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500} /></div>
</div>
</div>
</div>

View File

@@ -53,7 +53,7 @@ const SearchResults = React.createClass({
return (
<div className='search-results'>
<div className='search-results__header'>
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
<FormattedMessage id='search_results.total' defaultMessage='{count} {count, plural, one {result} other {results}}' values={{ count }} />
</div>
{accounts}

View File

@@ -100,7 +100,7 @@ const en = {
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"search_results.total": "{count} {count, plural, one {result} other {results}}",
"search.placeholder": "Search",
"search.status_by": "Status by {name}",
"status.delete": "Delete",

View File

@@ -81,7 +81,7 @@ const fr = {
"search.placeholder": "Rechercher",
"search.account": "Compte",
"search.hashtag": "Mot-clé",
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"search_results.total": "{count} {count, plural, one {résultat} other {résultats}}",
"search.status_by": "Statuts de {name}",
"upload_button.label": "Joindre un média",
"upload_form.undo": "Annuler",

View File

@@ -90,7 +90,7 @@ const hr = {
"report.placeholder": "Dodatni komentari",
"report.submit": "Pošalji",
"report.target": "Prijavljivanje",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"search_results.total": "{count} {count, plural, one {result} other {results}}",
"search.placeholder": "Traži",
"search.status_by": "Status od {name}",
"status.delete": "Obriši",

View File

@@ -18,7 +18,7 @@ const hu = {
"account.block": "Blokkolás",
"account.follow": "Követés",
"account.posts": "Posts",
"account.follows": "Követve",
"account.follows": "Követők",
"account.followers": "Követők",
"account.follows_you": "Követnek téged",
"getting_started.heading": "Első lépések",

View File

@@ -3,7 +3,6 @@ import de from './de';
import es from './es';
import hr from './hr';
import hu from './hu';
import it from './it';
import fr from './fr';
import nl from './nl';
import no from './no';
@@ -24,7 +23,6 @@ const locales = {
es,
hr,
hu,
it,
fr,
nl,
no,

View File

@@ -1,125 +0,0 @@
const it = {
"account.block": "Blocca @{name}",
"account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
"account.edit_profile": "Modifica profilo",
"account.follow": "Segui",
"account.followers": "Seguaci",
"account.follows_you": "Ti segue",
"account.follows": "Segue",
"account.mention": "Menziona @{name}",
"account.mute": "Silenzia @{name}",
"account.posts": "Posts",
"account.report": "Segnala @{name}",
"account.requested": "In attesa di approvazione",
"account.unblock": "Sblocca @{name}",
"account.unfollow": "Non seguire",
"account.unmute": "Non silenziare @{name}",
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
"column_back_button.label": "Indietro",
"column.blocks": "Utenti bloccati",
"column.community": "Timeline locale",
"column.favourites": "Apprezzati",
"column.follow_requests": "Richieste di amicizia",
"column.home": "Home",
"column.mutes": "Utenti silenziati",
"column.notifications": "Notifiche",
"column.public": "Timeline federata",
"compose_form.placeholder": "A cosa stai pensando?",
"compose_form.privacy_disclaimer": "Il tuo status privato verrà condiviso con gli utenti menzionati su {domains}. Ti fidi di {domainsCount, plural, one {quel server} other {quei server}}? Le impostazioni sulla privacy valgono solo su server Mastodon. Se {domains} {domainsCount, plural, one {non è un server Mastodon} other {non sono server Mastodon}}, non ci saranno indicazioni sulla privacy del tuo status, e potrebbe essere condiviso o reso visibile a destinatari indesiderati.",
"compose_form.publish": "Toot",
"compose_form.sensitive": "Segnala file come sensibile",
"compose_form.spoiler_placeholder": "Content warning",
"compose_form.spoiler": "Nascondi testo con avvertimento",
"emoji_button.label": "Inserisci emoji",
"empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
"empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
"empty_column.home.public_timeline": "la timeline pubblica",
"empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
"empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
"empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.",
"follow_request.authorize": "Autorizza",
"follow_request.reject": "Rifiuta",
"getting_started.apps": "Sono disponibili diverse app",
"getting_started.heading": "Come iniziare",
"getting_started.open_source_notice": "Mastodon è un software open source. Puoi contribuire o segnalare errori su GitHub all'indirizzo {github}. {apps}.",
"home.column_settings.advanced": "Avanzato",
"home.column_settings.basic": "Semplice",
"home.column_settings.filter_regex": "Filtra con espressioni regolari",
"home.column_settings.show_reblogs": "Mostra post condivisi",
"home.column_settings.show_replies": "Mostra risposte",
"home.settings": "Impostazioni colonna",
"lightbox.close": "Chiudi",
"loading_indicator.label": "Carico...",
"media_gallery.toggle_visible": "Imposta visibilità",
"missing_indicator.label": "Non trovato",
"navigation_bar.blocks": "Utenti bloccati",
"navigation_bar.community_timeline": "Timeline locale",
"navigation_bar.edit_profile": "Modifica profilo",
"navigation_bar.favourites": "Apprezzati",
"navigation_bar.follow_requests": "Richieste di amicizia",
"navigation_bar.info": "Informazioni estese",
"navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Utenti silenziati",
"navigation_bar.preferences": "Impostazioni",
"navigation_bar.public_timeline": "Timeline federata",
"notification.favourite": "{name} ha apprezzato il tuo post",
"notification.follow": "{name} ha iniziato a seguirti",
"notification.mention": "{name} ti ha menzionato",
"notification.reblog": "{name} ha condiviso il tuo post",
"notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
"notifications.clear": "Cancella notifiche",
"notifications.column_settings.alert": "Notifiche desktop",
"notifications.column_settings.favourite": "Apprezzati:",
"notifications.column_settings.follow": "Nuovi seguaci:",
"notifications.column_settings.mention": "Menzioni:",
"notifications.column_settings.reblog": "Post condivisi:",
"notifications.column_settings.show": "Mostra in colonna",
"notifications.column_settings.sound": "Riproduci suono",
"notifications.settings": "Impostazioni colonna",
"privacy.change": "Modifica privacy post",
"privacy.direct.long": "Invia solo a utenti menzionati",
"privacy.direct.short": "Diretto",
"privacy.private.long": "Invia solo ai seguaci",
"privacy.private.short": "Privato",
"privacy.public.long": "Invia alla timeline pubblica",
"privacy.public.short": "Pubblico",
"privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
"privacy.unlisted.short": "Non elencato",
"reply_indicator.cancel": "Annulla",
"report.heading": "Nuova segnalazione",
"report.placeholder": "Commenti aggiuntivi",
"report.submit": "Invia",
"report.target": "Invio la segnalazione",
"search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
"search.placeholder": "Cerca",
"search.status_by": "Status per {name}",
"status.delete": "Elimina",
"status.favourite": "Apprezzato",
"status.load_more": "Mostra di più",
"status.media_hidden": "Allegato nascosto",
"status.mention": "Nomina @{name}",
"status.open": "Espandi questo post",
"status.reblog": "Condividi",
"status.reblogged_by": "{name} ha condiviso",
"status.reply": "Rispondi",
"status.report": "Segnala @{name}",
"status.sensitive_toggle": "Clicca per vedere",
"status.sensitive_warning": "Materiale sensibile",
"status.show_less": "Mostra meno",
"status.show_more": "Mostra di più",
"tabs_bar.compose": "Scrivi",
"tabs_bar.federated_timeline": "Federazione",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Locale",
"tabs_bar.notifications": "Notifiche",
"upload_area.title": "Trascina per caricare",
"upload_button.label": "Aggiungi file multimediale",
"upload_form.undo": "Annulla",
"upload_progress.label": "Sto caricando...",
"video_player.toggle_sound": "Attiva suono",
"video_player.toggle_visible": "Attiva visibilità",
"video_player.expand": "Espandi video",
"video_player.video_error": "Il video non può essere riprodotto",
};
export default it;

View File

@@ -110,7 +110,7 @@ const ja = {
"report.target": "問題のユーザー",
"search.placeholder": "検索",
"search.status_by": "{name}からの投稿",
"search_results.total": "{count, number} 件の結果",
"search_results.total": "{count} 件",
"status.delete": "削除",
"status.favourite": "お気に入り",
"status.load_more": "もっと見る",

View File

@@ -59,7 +59,7 @@ const nl = {
"search.placeholder": "Zoeken",
"search.account": "Account",
"search.hashtag": "Hashtag",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"search_results.total": "{count} {count, plural, one {resultaat} other {resultaten}}",
"upload_button.label": "Media toevoegen",
"upload_form.undo": "Ongedaan maken",
"notification.follow": "{name} volgt jou nu",

View File

@@ -87,7 +87,7 @@ const no = {
"report.placeholder": "Tilleggskommentarer",
"report.submit": "Send inn",
"report.target": "Rapporterer",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
"search_results.total": "{count} {count, plural, one {resultat} other {resultater}}",
"search.placeholder": "Søk",
"search.status_by": "Status fra {name}",
"status.delete": "Slett",

View File

@@ -80,7 +80,7 @@ const oc = {
"search.placeholder": "Recercar",
"search.account": "Compte",
"search.hashtag": "Mot-clau",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"search_results.total": "{count} {count, plural, one {resultat} other {resultats}}",
"search.status_by": "Estatuts de {name}",
"upload_button.label": "Apondre un mèdia",
"upload_form.undo": "Anullar",

View File

@@ -90,7 +90,7 @@ const pt_br = {
"report.placeholder": "Comentários adicionais",
"report.submit": "Enviar",
"report.target": "Denunciar",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
"search.placeholder": "Pesquisar",
"search.status_by": "Post de {name}",
"status.delete": "Eliminar",

View File

@@ -90,7 +90,7 @@ const pt = {
"report.placeholder": "Comentários adicionais",
"report.submit": "Enviar",
"report.target": "Denunciar",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
"search.placeholder": "Pesquisar",
"search.status_by": "Post de {name}",
"status.delete": "Eliminar",

View File

@@ -92,7 +92,7 @@ const ru = {
"report.target": "Жалуемся на",
"search.placeholder": "Поиск",
"search.status_by": "Статус от {name}",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"search_results.total": "{count} {count, plural, one {result} other {results}}",
"status.delete": "Удалить",
"status.favourite": "Нравится",
"status.load_more": "Показать еще",

View File

@@ -119,7 +119,7 @@ const zh_cn = {
"report.placeholder": "额外消息",
"report.submit": "提交",
"report.target": "Reporting",
"search_results.total": "{count, number} 项结果",
"search_results.total": "{count} 项结果",
"search.account": "用户",
"search.hashtag": "标签",
"search.placeholder": "搜索",

View File

@@ -112,7 +112,7 @@ const zh_hk = {
"report.placeholder": "額外訊息",
"report.submit": "提交",
"report.target": "Reporting",
"search_results.total": "{count, number} 項結果",
"search_results.total": "{count} 項結果",
"search.account": "用戶",
"search.hashtag": "標籤",
"search.placeholder": "搜尋",

View File

@@ -237,14 +237,13 @@
}
.accounts-grid {
clear: both;
box-shadow: 0 0 15px rgba($color8, 0.2);
background: $color5;
border-radius: 0 0 4px 4px;
padding: 20px 10px;
padding-bottom: 10px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
@media screen and (max-width: 700px) {
border-radius: 0;
@@ -254,9 +253,11 @@
.account-grid-card {
box-sizing: border-box;
width: 335px;
float: left;
border: 1px solid $color2;
border-radius: 4px;
color: $color1;
height: 160px;
margin-bottom: 10px;
&:nth-child(odd) {

View File

@@ -135,6 +135,10 @@ class Account < ApplicationRecord
!subscription_expires_at.blank?
end
def followers_domains
followers.reorder(nil).pluck('distinct accounts.domain')
end
def favourited?(status)
status.proper.favourites.where(account: self).count.positive?
end

View File

@@ -10,7 +10,7 @@ class FollowRemoteAccountService < BaseService
# important information from their feed
# @param [String] uri User URI in the form of username@domain
# @return [Account]
def call(uri, redirected = nil)
def call(uri)
username, domain = uri.split('@')
return Account.find_local(username) if TagManager.instance.local_domain?(domain)
@@ -24,14 +24,8 @@ class FollowRemoteAccountService < BaseService
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?
# Disallow account hijacking
confirmed_username, confirmed_domain = data.subject.gsub(/\Aacct:/, '').split('@')
unless confirmed_username.casecmp(username).zero? && confirmed_domain.casecmp(domain).zero?
return call("#{confirmed_username}@#{confirmed_domain}", true) if redirected.nil?
raise Goldfinger::Error, 'Requested and returned acct URI do not match'
end
return Account.find_local(confirmed_username) if TagManager.instance.local_domain?(confirmed_domain)
confirmed_account = Account.find_remote(confirmed_username, confirmed_domain)

View File

@@ -1,7 +0,0 @@
%meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:title', content: [yield(:page_title).strip.presence, site_title].compact.join(' - ') }/
%meta{ property: 'og:description', content: account.note }/
%meta{ property: 'og:image', content: full_asset_url(account.avatar.url(:original)) }/
%meta{ property: 'og:image:width', content: '120' }/
%meta{ property: 'og:image:height', content: '120' }/
%meta{ property: 'twitter:card', content: 'summary' }/

View File

@@ -0,0 +1,12 @@
- content_for :page_title do
= t('accounts.people_who_follow', name: display_name(@account))
= render 'header', account: @account
.accounts-grid
- if @followers.empty?
= render 'nothing_here'
- else
= render partial: 'grid_card', collection: @followers, as: :account, cached: true
= paginate @followers

View File

@@ -0,0 +1,12 @@
- content_for :page_title do
= t('accounts.people_followed_by', name: display_name(@account))
= render 'header', account: @account
.accounts-grid
- if @following.empty?
= render 'nothing_here'
- else
= render partial: 'grid_card', collection: @following, as: :account, cached: true
= paginate @following

View File

@@ -5,8 +5,14 @@
%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: site_title }/
%meta{ property: 'og:type', content: 'profile' }/
= render 'og', account: @account
%meta{ property: 'og:title', content: "#{@account.username} on #{site_hostname}" }/
%meta{ property: 'og:description', content: @account.note }/
%meta{ property: 'og:image', content: full_asset_url(@account.avatar.url(:original)) }/
%meta{ property: 'og:image:width', content: '120' }/
%meta{ property: 'og:image:height', content: '120' }/
%meta{ property: 'twitter:card', content: 'summary' }/
- if show_landing_strip?
= render partial: 'shared/landing_strip', locals: { account: @account }

View File

@@ -1,9 +1,6 @@
- content_for :page_title do
= t('accounts.people_who_follow', name: display_name(@account))
- content_for :header_tags do
= render 'accounts/og', account: @account
= render 'accounts/header', account: @account
= render 'accounts/follow_grid', accounts: @accounts

View File

@@ -1,9 +1,6 @@
- content_for :page_title do
= t('accounts.people_followed_by', name: display_name(@account))
- content_for :header_tags do
= render 'accounts/og', account: @account
= render 'accounts/header', account: @account
= render 'accounts/follow_grid', accounts: @accounts

View File

@@ -14,7 +14,7 @@
%title<
- if content_for?(:page_title)
= yield(:page_title)
= yield(:page_title).strip
= ' - '
= site_title

View File

@@ -8,12 +8,14 @@ class Pubsubhubbub::DistributionWorker
def perform(stream_entry_id)
stream_entry = StreamEntry.find(stream_entry_id)
return if stream_entry.hidden?
return if stream_entry.status.direct_visibility?
account = stream_entry.account
payload = AtomSerializer.render(AtomSerializer.new.feed(account, [stream_entry]))
domains = account.followers_domains
Subscription.where(account: account).active.select('id, callback_url').find_each do |subscription|
next unless domains.include?(Addressable::URI.parse(subscription.callback_url).host)
Pubsubhubbub::DeliveryWorker.perform_async(subscription.id, payload)
end
rescue ActiveRecord::RecordNotFound

View File

@@ -3,35 +3,10 @@ require 'rails_helper'
RSpec.describe FollowRemoteAccountService do
subject { FollowRemoteAccountService.new }
before do
stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt'))
stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
end
it 'raises error if no such user can be resolved via webfinger' do
expect { subject.call('catsrgr8@quitter.no') }.to raise_error Goldfinger::Error
end
it 'raises error if the domain does not have webfinger' do
expect { subject.call('catsrgr8@example.com') }.to raise_error Goldfinger::Error
end
it 'returns an already existing remote account' do
old_account = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
returned_account = subject.call('gargron@quitter.no')
expect(old_account.id).to eq returned_account.id
end
it 'returns a new remote account' do
account = subject.call('gargron@quitter.no')
expect(account.username).to eq 'gargron'
expect(account.domain).to eq 'quitter.no'
expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
end
it 'returns nil if no such user can be resolved via webfinger'
it 'returns nil if the domain does not have webfinger'
it 'returns nil if remote user does not offer a hub URL'
it 'returns an already existing remote account'
it 'returns a new remote account'
it 'fills the remote account with profile information'
end