Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
	Dockerfile
	app/javascript/packs/common.js
	config/webpack/loaders/sass.js
	config/webpack/shared.js
	db/schema.rb
	package.json
	yarn.lock

A lot of the conflicts come from updating webpack.

Even though upstream deleted app/javascript/packs/common.js, I kept
glitch-soc's version as it unifies JS/CSS packs behavior across flavours.

Ported glitch changes to webpack 4.x
This commit is contained in:
Thibaut Girka
2018-07-15 18:17:37 +02:00
79 changed files with 3212 additions and 1803 deletions

View File

@@ -0,0 +1,58 @@
# frozen_string_literal: true
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
def index
authorize :relay, :update?
@relays = Relay.all
end
def new
authorize :relay, :update?
@relay = Relay.new(inbox_url: Relay::PRESET_RELAY)
end
def create
authorize :relay, :update?
@relay = Relay.new(resource_params)
if @relay.save
@relay.enable!
redirect_to admin_relays_path
else
render action: :new
end
end
def destroy
authorize :relay, :update?
@relay.destroy
redirect_to admin_relays_path
end
def enable
authorize :relay, :update?
@relay.enable!
redirect_to admin_relays_path
end
def disable
authorize :relay, :update?
@relay.disable!
redirect_to admin_relays_path
end
private
def set_relay
@relay = Relay.find(params[:id])
end
def resource_params
params.require(:relay).permit(:inbox_url)
end
end
end

View File

@@ -67,7 +67,7 @@ module StreamEntriesHelper
end
def acct(account)
if embedded_view? && account.local?
if account.local?
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
else
"@#{account.acct}"

View File

@@ -1,6 +1,9 @@
// This file will be loaded on admin pages, regardless of theme.
import { delegate } from 'rails-ujs';
import { start } from '../mastodon/common';
start();
function handleDeleteStatus(event) {
const [data] = event.detail;

View File

@@ -128,7 +128,7 @@ export function expandDomainBlocks() {
return (dispatch, getState) => {
const url = getState().getIn(['domain_lists', 'blocks', 'next']);
if (url === null) {
if (!url) {
return;
}

View File

@@ -0,0 +1,8 @@
import Rails from 'rails-ujs';
export function start() {
require('font-awesome/css/font-awesome.css');
require.context('../images/', true);
Rails.start();
};

View File

@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me, invitesEnabled } from '../../initial_state';
import { me, invitesEnabled, version } from '../../initial_state';
import { fetchFollowRequests } from '../../actions/accounts';
import { List as ImmutableList } from 'immutable';
import { Link } from 'react-router-dom';
@@ -149,7 +149,7 @@ export default class GettingStarted extends ImmutablePureComponent {
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }}
values={{ github: <span><a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> (v{version})</span> }}
/>
</p>
</div>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { LoadingBar } from 'react-redux-loading-bar';
import ZoomableImage from './zoomable_image';
export default class ImageLoader extends React.PureComponent {
@@ -23,6 +24,7 @@ export default class ImageLoader extends React.PureComponent {
state = {
loading: true,
error: false,
width: null,
}
removers = [];
@@ -122,6 +124,7 @@ export default class ImageLoader extends React.PureComponent {
setCanvasRef = c => {
this.canvas = c;
if (c) this.setState({ width: c.offsetWidth });
}
render () {
@@ -135,6 +138,7 @@ export default class ImageLoader extends React.PureComponent {
return (
<div className={className}>
<LoadingBar loading={loading ? 1 : 0} className='loading-bar' style={{ width: this.state.width || width }} />
{loading ? (
<canvas
className='image-loader__preview-canvas'

View File

@@ -13,5 +13,6 @@ export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const maxChars = getMeta('max_toot_chars') || 500;
export const invitesEnabled = getMeta('invites_enabled');
export const version = getMeta('version');
export default initialState;

View File

@@ -6,10 +6,10 @@
"account.direct": "Direct message @{name}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edit profile",
"account.follow": "Follow",
"account.followers": "Followers",
"account.follows": "Follows",
"account.edit_profile": "Uprav profil",
"account.follow": "Sleduj",
"account.followers": "Sledovatelé",
"account.follows": "Sleduje",
"account.follows_you": "Follows you",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.media": "Media",

View File

@@ -65,7 +65,7 @@
"compose_form.hashtag_warning": "Αυτό το τουτ δεν θα εμφανίζεται κάτω από κανένα hashtag καθώς είναι αφανές. Μόνο τα δημόσια τουτ μπορούν να αναζητηθούν ανά hashtag.",
"compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σας προς τους ακολούθους σας.",
"compose_form.lock_disclaimer.lock": "κλειδωμένος",
"compose_form.placeholder": "Τι έχεις στο μυαλό σου;",
"compose_form.placeholder": "Τι σκέφτεσαι;",
"compose_form.publish": "Τουτ",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.marked": "Το πολυμέσο έχει σημειωθεί ως ευαίσθητο",

View File

@@ -166,7 +166,7 @@
"navigation_bar.domain_blocks": "دامین‌های پنهان‌شده",
"navigation_bar.edit_profile": "ویرایش نمایه",
"navigation_bar.favourites": "پسندیده‌ها",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "واژگان بی‌صداشده",
"navigation_bar.follow_requests": "درخواست‌های پیگیری",
"navigation_bar.info": "اطلاعات تکمیلی",
"navigation_bar.keyboard_shortcuts": "میان‌برهای صفحه‌کلید",

View File

@@ -170,7 +170,7 @@
"navigation_bar.domain_blocks": "非表示にしたドメイン",
"navigation_bar.edit_profile": "プロフィールを編集",
"navigation_bar.favourites": "お気に入り",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "フィルター設定",
"navigation_bar.follow_requests": "フォローリクエスト",
"navigation_bar.info": "このインスタンスについて",
"navigation_bar.keyboard_shortcuts": "ホットキー",

View File

@@ -166,7 +166,7 @@
"navigation_bar.domain_blocks": "Verborgen domeinen",
"navigation_bar.edit_profile": "Profiel bewerken",
"navigation_bar.favourites": "Favorieten",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "Genegeerde woorden",
"navigation_bar.follow_requests": "Volgverzoeken",
"navigation_bar.info": "Over deze server",
"navigation_bar.keyboard_shortcuts": "Sneltoetsen",

View File

@@ -166,7 +166,7 @@
"navigation_bar.domain_blocks": "Domenis resconduts",
"navigation_bar.edit_profile": "Modificar lo perfil",
"navigation_bar.favourites": "Favorits",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "Mots ignorats",
"navigation_bar.follow_requests": "Demandas dabonament",
"navigation_bar.info": "Mai informacions",
"navigation_bar.keyboard_shortcuts": "Acorchis clavièr",

View File

@@ -166,7 +166,7 @@
"navigation_bar.domain_blocks": "Domínios escondidos",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.filters": "Muted words",
"navigation_bar.filters": "Palavras silenciadas",
"navigation_bar.follow_requests": "Seguidores pendentes",
"navigation_bar.info": "Mais informações",
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado",

View File

@@ -7,7 +7,7 @@
"account.disclaimer_full": "Inofrmácie uvedené nižšie nemusia byť úplným odrazom uživateľovho účtu.",
"account.domain_blocked": "Doména ukrytá",
"account.edit_profile": "Upraviť profil",
"account.follow": "Následovať",
"account.follow": "Následuj",
"account.followers": "Sledujúci",
"account.follows": "Následuje",
"account.follows_you": "Následuje ťa",

View File

@@ -1,4 +1,7 @@
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
start();
function loaded() {
const TimelineContainer = require('../mastodon/containers/timeline_container').default;

View File

@@ -1,4 +1,7 @@
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
start();
loadPolyfills().then(() => {
require('../mastodon/main').default();

View File

@@ -1,5 +1,8 @@
import loadPolyfills from '../mastodon/load_polyfills';
import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
start();
function main() {
const IntlRelativeFormat = require('intl-relativeformat').default;

View File

@@ -1,4 +1,7 @@
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
start();
function loaded() {
const ComposeContainer = require('../mastodon/containers/compose_container').default;

View File

@@ -165,6 +165,11 @@
color: $valid-value-color;
font-weight: 500;
}
.negative-hint {
color: $error-value-color;
font-weight: 500;
}
}
.simple_form {

View File

@@ -1478,6 +1478,7 @@ a.account__display-name {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.image-loader__preview-canvas {
max-width: $media-modal-media-max-width;
@@ -1486,8 +1487,8 @@ a.account__display-name {
object-fit: contain;
}
&.image-loader--loading .image-loader__preview-canvas {
filter: blur(2px);
.loading-bar {
position: relative;
}
&.image-loader--amorphous .image-loader__preview-canvas {

View File

@@ -3,12 +3,16 @@
class LanguageDetector
include Singleton
CHARACTER_THRESHOLD = 140
def initialize
@identifier = CLD3::NNetLanguageIdentifier.new(1, 2048)
end
def detect(text, account)
detect_language_code(text) || default_locale(account)
input_text = prepare_text(text)
return if input_text.blank?
detect_language_code(input_text) || default_locale(account)
end
def language_names
@@ -23,8 +27,13 @@ class LanguageDetector
simplify_text(text).strip
end
def unreliable_input?(text)
text.size < CHARACTER_THRESHOLD
end
def detect_language_code(text)
result = @identifier.find_language(prepare_text(text))
return if unreliable_input?(text)
result = @identifier.find_language(text)
iso6391(result.language.to_s).to_sym if result.reliable?
end
@@ -66,6 +75,6 @@ class LanguageDetector
end
def default_locale(account)
account.user_locale&.to_sym
account.user_locale&.to_sym || I18n.default_locale
end
end

View File

@@ -12,6 +12,8 @@ class PotentialFriendshipTracker
class << self
def record(account_id, target_account_id, action)
return if account_id == target_account_id
key = "interactions:#{account_id}"
weight = WEIGHTS[action]

74
app/models/relay.rb Normal file
View File

@@ -0,0 +1,74 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: relays
#
# id :bigint(8) not null, primary key
# inbox_url :string default(""), not null
# enabled :boolean default(FALSE), not null
# follow_activity_id :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Relay < ApplicationRecord
PRESET_RELAY = 'https://relay.joinmastodon.org/inbox'
validates :inbox_url, presence: true, uniqueness: true, url: true, if: :will_save_change_to_inbox_url?
scope :enabled, -> { where(enabled: true) }
before_destroy :ensure_disabled
def enable!
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
payload = Oj.dump(follow_activity(activity_id))
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
update(enabled: true, follow_activity_id: activity_id)
end
def disable!
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
payload = Oj.dump(unfollow_activity(activity_id))
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
update(enabled: false, follow_activity_id: nil)
end
private
def follow_activity(activity_id)
{
'@context': ActivityPub::TagManager::CONTEXT,
id: activity_id,
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
object: ActivityPub::TagManager::COLLECTIONS[:public],
}
end
def unfollow_activity(activity_id)
{
'@context': ActivityPub::TagManager::CONTEXT,
id: activity_id,
type: 'Undo',
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
object: {
id: follow_activity_id,
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
object: ActivityPub::TagManager::COLLECTIONS[:public],
},
}
end
def some_local_account
@some_local_account ||= Account.local.find_by(suspended: false)
end
def ensure_disabled
return unless enabled?
disable!
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class RelayPolicy < ApplicationPolicy
def update?
admin?
end
end

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer
attributes :id, :type, :actor
attributes :id, :type, :actor, :to
attribute :virtual_object, key: :object
def id
@@ -19,4 +19,8 @@ class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer
def virtual_object
actor
end
def to
[ActivityPub::TagManager::COLLECTIONS[:public]]
end
end

View File

@@ -17,7 +17,7 @@ class ActivityPub::DeleteSerializer < ActiveModel::Serializer
end
end
attributes :id, :type, :actor
attributes :id, :type, :actor, :to
has_one :object, serializer: TombstoneSerializer
@@ -32,4 +32,8 @@ class ActivityPub::DeleteSerializer < ActiveModel::Serializer
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def to
[ActivityPub::TagManager::COLLECTIONS[:public]]
end
end

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ActivityPub::UndoAnnounceSerializer < ActiveModel::Serializer
attributes :id, :type, :actor
attributes :id, :type, :actor, :to
has_one :object, serializer: ActivityPub::ActivitySerializer
@@ -16,4 +16,8 @@ class ActivityPub::UndoAnnounceSerializer < ActiveModel::Serializer
def actor
ActivityPub::TagManager.instance.uri_for(object.account)
end
def to
[ActivityPub::TagManager::COLLECTIONS[:public]]
end
end

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ActivityPub::UpdateSerializer < ActiveModel::Serializer
attributes :id, :type, :actor
attributes :id, :type, :actor, :to
has_one :object, serializer: ActivityPub::ActorSerializer
@@ -16,4 +16,8 @@ class ActivityPub::UpdateSerializer < ActiveModel::Serializer
def actor
ActivityPub::TagManager.instance.uri_for(object)
end
def to
[ActivityPub::TagManager::COLLECTIONS[:public]]
end
end

View File

@@ -19,6 +19,7 @@ class InitialStateSerializer < ActiveModel::Serializer
domain: Rails.configuration.x.local_domain,
admin: object.admin&.id&.to_s,
search_enabled: Chewy.enabled?,
version: Mastodon::Version.to_s,
invites_enabled: Setting.min_invite_role == 'user',
}

View File

@@ -90,6 +90,18 @@ class RemoveStatusService < BaseService
ActivityPub::DeliveryWorker.push_bulk(@account.followers.inboxes) do |inbox_url|
[signed_activity_json, @account.id, inbox_url]
end
relay! if relayable?
end
def relayable?
@status.public_visibility?
end
def relay!
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
[signed_activity_json, @account.id, inbox_url]
end
end
def salmon_xml

View File

@@ -22,7 +22,13 @@ class SuspendAccountService < BaseService
end
def purge_content!
ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id) if @account.local?
if @account.local?
ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id)
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
[delete_actor_json, @account.id, inbox_url]
end
end
@account.statuses.reorder(nil).find_in_batches do |statuses|
BatchedRemoveStatusService.new.call(statuses)
@@ -59,12 +65,14 @@ class SuspendAccountService < BaseService
end
def delete_actor_json
return @delete_actor_json if defined?(@delete_actor_json)
payload = ActiveModelSerializers::SerializableResource.new(
@account,
serializer: ActivityPub::DeleteActorSerializer,
adapter: ActivityPub::Adapter
).as_json
Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
@delete_actor_json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
end
end

View File

@@ -0,0 +1,21 @@
%tr
%td
%samp= relay.inbox_url
%td
- if relay.enabled?
%span.positive-hint
= fa_icon('check')
= ' '
= t 'admin.relays.enabled'
- else
%span.negative-hint
= fa_icon('times')
= ' '
= t 'admin.relays.disabled'
%td
- if relay.enabled?
= table_link_to 'power-off', t('admin.relays.disable'), disable_admin_relay_path(relay), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
- else
= table_link_to 'power-off', t('admin.relays.enable'), enable_admin_relay_path(relay), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
= table_link_to 'times', t('admin.relays.delete'), admin_relay_path(relay), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

View File

@@ -0,0 +1,20 @@
- content_for :page_title do
= t('admin.relays.title')
.simple_form
%p.hint= t('admin.relays.description_html')
= link_to @relays.empty? ? t('admin.relays.setup') : t('admin.relays.add_new'), new_admin_relay_path, class: 'block-button'
- unless @relays.empty?
%hr.spacer
.table-wrapper
%table.table
%thead
%tr
%th= t('admin.relays.inbox_url')
%th= t('admin.relays.status')
%th
%tbody
= render @relays

View File

@@ -0,0 +1,13 @@
- content_for :page_title do
= t('admin.relays.add_new')
= simple_form_for @relay, url: admin_relays_path do |f|
= render 'shared/error_messages', object: @relay
.field-group
= f.input :inbox_url, as: :string, wrapper: :with_block_label
.actions
= f.button :button, t('admin.relays.save_and_enable'), type: :submit
%p.hint.subtle-hint= t('admin.relays.enable_hint')

View File

@@ -14,6 +14,8 @@ class ActivityPub::DistributionWorker
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
[signed_payload, @account.id, inbox_url]
end
relay! if relayable?
rescue ActiveRecord::RecordNotFound
true
end
@@ -24,6 +26,10 @@ class ActivityPub::DistributionWorker
@status.direct_visibility?
end
def relayable?
@status.public_visibility?
end
def inboxes
@inboxes ||= @account.followers.inboxes
end
@@ -39,4 +45,10 @@ class ActivityPub::DistributionWorker
adapter: ActivityPub::Adapter
).as_json
end
def relay!
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
[signed_payload, @account.id, inbox_url]
end
end
end

View File

@@ -9,7 +9,11 @@ class ActivityPub::UpdateDistributionWorker
@account = Account.find(account_id)
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
[payload, @account.id, inbox_url]
[signed_payload, @account.id, inbox_url]
end
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
[signed_payload, @account.id, inbox_url]
end
rescue ActiveRecord::RecordNotFound
true
@@ -21,6 +25,10 @@ class ActivityPub::UpdateDistributionWorker
@inboxes ||= @account.followers.inboxes
end
def signed_payload
@signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
end
def payload
@payload ||= ActiveModelSerializers::SerializableResource.new(
@account,