diff --git a/Dockerfile b/Dockerfile
index 46631cde410..3acbc9d4ce4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,6 @@ SHELL ["bash", "-c"]
ENV NODE_VER="8.15.0"
RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \
- apt -y dist-upgrade && \
apt -y install wget make gcc g++ python && \
cd ~ && \
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
@@ -80,7 +79,6 @@ ARG GID=991
RUN apt update && \
echo "Etc/UTC" > /etc/localtime && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
- apt -y dist-upgrade && \
apt install -y whois wget && \
addgroup --gid $GID mastodon && \
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
diff --git a/Gemfile.lock b/Gemfile.lock
index 4b29a2be8b6..7fb6e81107b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -384,7 +384,7 @@ GEM
addressable (~> 2.5)
http (~> 3.0)
nokogiri (~> 1.8)
- ox (2.10.0)
+ ox (2.10.1)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb
index 1a19bd0ef6e..1b658f87083 100644
--- a/app/controllers/api/v1/push/subscriptions_controller.rb
+++ b/app/controllers/api/v1/push/subscriptions_controller.rb
@@ -51,6 +51,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params
return {} if params[:data].blank?
- params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
+ params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll])
end
end
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index fe8e4258088..d8153e082fe 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
favourite: alerts_enabled,
reblog: alerts_enabled,
mention: alerts_enabled,
+ poll: alerts_enabled,
},
}
@@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
- @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
+ @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll])
end
end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index 6e646ab8401..e59c396558f 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -16,24 +16,28 @@ module StreamEntriesHelper
if user_signed_in?
if account.id == current_user.account_id
link_to settings_profile_url, class: 'button logo-button' do
- safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')])
+ safe_join([svg_logo, t('settings.edit_profile')])
end
elsif current_account.following?(account) || current_account.requested?(account)
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
- safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
+ safe_join([svg_logo, t('accounts.unfollow')])
end
elsif !(account.memorial? || account.moved?)
link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
- safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
+ safe_join([svg_logo, t('accounts.follow')])
end
end
elsif !(account.memorial? || account.moved?)
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
- safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
+ safe_join([svg_logo, t('accounts.follow')])
end
end
end
+ def svg_logo
+ content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
+ end
+
def account_badge(account, all: false)
if account.bot?
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
diff --git a/app/javascript/images/logo_transparent.svg b/app/javascript/images/logo_transparent.svg
index abd6d1f67d0..a1e7b403e03 100644
--- a/app/javascript/images/logo_transparent.svg
+++ b/app/javascript/images/logo_transparent.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js
index acab107a165..690f9ae5a07 100644
--- a/app/javascript/mastodon/components/poll.js
+++ b/app/javascript/mastodon/components/poll.js
@@ -28,7 +28,6 @@ class Poll extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func,
disabled: PropTypes.bool,
- visible: PropTypes.bool,
};
state = {
@@ -70,14 +69,13 @@ class Poll extends ImmutablePureComponent {
};
renderOption (option, optionIndex) {
- const { poll, disabled, visible } = this.props;
- const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
- const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
- const active = !!this.state.selected[`${optionIndex}`];
- const showResults = poll.get('voted') || poll.get('expired');
+ const { poll, disabled } = this.props;
+ const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
+ const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
+ const active = !!this.state.selected[`${optionIndex}`];
+ const showResults = poll.get('voted') || poll.get('expired');
let titleEmojified = option.get('title_emojified');
-
if (!titleEmojified) {
const emojiMap = makeEmojiMap(poll);
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
@@ -106,7 +104,7 @@ class Poll extends ImmutablePureComponent {
{!showResults && }
{showResults && {Math.round(percent)}%}
- {visible ? : {String.fromCharCode(64 + optionIndex + 1)}}
+
);
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index b67354b01ab..28738105a6e 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -317,7 +317,7 @@ class Status extends ImmutablePureComponent {
}
if (status.get('poll')) {
- media = ;
+ media = ;
} else if (status.get('media_attachments').size > 0) {
if (this.props.muted) {
media = (
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index 129ce77f62f..fc7840ec190 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -7,12 +7,12 @@ 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, version, profile_directory, repository, source_url } from '../../initial_state';
+import { me, profile_directory } from '../../initial_state';
import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { List as ImmutableList } from 'immutable';
-import { Link } from 'react-router-dom';
import NavigationBar from '../compose/components/navigation_bar';
import Icon from 'mastodon/components/icon';
+import LinkFooter from 'mastodon/features/ui/components/link_footer';
const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
@@ -166,27 +166,7 @@ class GettingStarted extends ImmutablePureComponent {
{!multiColumn &&
}
-
-
- {invitesEnabled && - ·
}
- {multiColumn && - ·
}
- - ·
- - ·
- - ·
- - ·
- - ·
- - ·
-
-
-
-
- {repository} (v{version}) }}
- />
-
-
+
);
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 22821af0c10..9089eb303f4 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -108,7 +108,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
}
if (status.get('poll')) {
- media = ;
+ media = ;
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js
index 7c1158e5d23..c456a64003d 100644
--- a/app/javascript/mastodon/features/ui/components/compose_panel.js
+++ b/app/javascript/mastodon/features/ui/components/compose_panel.js
@@ -2,9 +2,7 @@ import React from 'react';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import NavigationContainer from 'mastodon/features/compose/containers/navigation_container';
-import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
-import { Link } from 'react-router-dom';
-import { FormattedMessage } from 'react-intl';
+import LinkFooter from './link_footer';
const ComposePanel = () => (
@@ -14,27 +12,7 @@ const ComposePanel = () => (
-
-
- {invitesEnabled && - ·
}
- - ·
- - ·
- - ·
- - ·
- - ·
- - ·
- - ·
-
-
-
-
- {repository} (v{version}) }}
- />
-
-
+
);
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js
new file mode 100644
index 00000000000..b481983dc88
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/link_footer.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router-dom';
+import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
+
+const LinkFooter = ({ withHotkeys }) => (
+
+
+ {invitesEnabled && - ·
}
+ {withHotkeys && - ·
}
+ - ·
+ - ·
+ - ·
+ - ·
+ - ·
+ - ·
+
+
+
+
+ {repository} (v{version}) }}
+ />
+
+
+);
+
+LinkFooter.propTypes = {
+ withHotkeys: PropTypes.bool,
+};
+
+export default LinkFooter;
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index c891f4a522d..4d9604de9ee 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -18,7 +18,7 @@ import compareId from '../compare_id';
const initialState = ImmutableMap({
items: ImmutableList(),
hasMore: true,
- top: true,
+ top: false,
unread: 0,
isLoading: false,
});
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 48236a286e1..d35a598211f 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -306,7 +306,7 @@
.button.logo-button {
color: $white;
- svg path:first-child {
+ svg {
fill: $white;
}
}
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 368c2304bcc..0eae4939f28 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -359,10 +359,6 @@
.logo-button {
background-color: $secondary-text-color;
-
- svg path:last-child {
- fill: $secondary-text-color;
- }
}
}
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index 4d75477e09b..f74c004e995 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -122,10 +122,7 @@
height: 36px;
width: auto;
margin: 0 auto;
-
- path {
- fill: lighten($ui-base-color, 34%);
- }
+ fill: lighten($ui-base-color, 34%);
}
&:hover,
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index 63eeffe2526..bfbb907e03c 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -89,40 +89,21 @@
height: auto;
vertical-align: middle;
margin-right: 5px;
-
- path:first-child {
- fill: $primary-text-color;
- }
-
- path:last-child {
- fill: $ui-highlight-color;
- }
+ fill: $primary-text-color;
}
&:active,
&:focus,
&:hover {
background: lighten($ui-highlight-color, 10%);
-
- svg path:last-child {
- fill: lighten($ui-highlight-color, 10%);
- }
}
&:disabled,
&.disabled {
- svg path:last-child {
- fill: $ui-primary-color;
- }
-
&:active,
&:focus,
&:hover {
background: $ui-primary-color;
-
- svg path:last-child {
- fill: $ui-primary-color;
- }
}
}
@@ -131,10 +112,6 @@
&:focus,
&:hover {
background: $error-red;
-
- svg path:last-child {
- fill: $error-red;
- }
}
}
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 34c25a7d1a3..f11fddd4d96 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -39,3 +39,6 @@
%body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield
+
+ %div{ style: 'display: none'}
+ = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index b4a21caf16e..37808cdd09a 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -35,9 +35,7 @@
%li= link_to t('about.api'), 'https://docs.joinmastodon.org/api/guidelines/'
.column-2
%h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/'
-
- = link_to root_url, class: 'brand' do
- = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
+ = link_to svg_logo, root_url, class: 'brand'
.column-3
%h4= site_hostname
%ul
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index ae3eede6685..24ba16ae36f 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -13,6 +13,10 @@ class Rack::Attack
)
end
+ def remote_ip
+ @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
+ end
+
def authenticated_user_id
authenticated_token&.resource_owner_id
end
@@ -28,6 +32,10 @@ class Rack::Attack
def web_request?
!api_request?
end
+
+ def paging_request?
+ params['page'].present? || params['min_id'].present? || params['max_id'].present? || params['since_id'].present?
+ end
end
PROTECTED_PATHS = %w(
@@ -42,15 +50,15 @@ class Rack::Attack
# (blocklist & throttles are skipped)
Rack::Attack.safelist('allow from localhost') do |req|
# Requests are allowed if the return value is truthy
- req.ip == '127.0.0.1' || req.ip == '::1'
+ req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
end
throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req|
req.authenticated_user_id if req.api_request?
end
- throttle('throttle_unauthenticated_api', limit: 7_500, period: 5.minutes) do |req|
- req.ip if req.api_request?
+ throttle('throttle_unauthenticated_api', limit: 300, period: 5.minutes) do |req|
+ req.remote_ip if req.api_request? && req.unauthenticated?
end
throttle('throttle_api_media', limit: 30, period: 30.minutes) do |req|
@@ -58,11 +66,20 @@ class Rack::Attack
end
throttle('throttle_media_proxy', limit: 30, period: 30.minutes) do |req|
- req.ip if req.path.start_with?('/media_proxy')
+ req.remote_ip if req.path.start_with?('/media_proxy')
end
throttle('throttle_api_sign_up', limit: 5, period: 30.minutes) do |req|
- req.ip if req.post? && req.path == '/api/v1/accounts'
+ req.remote_ip if req.post? && req.path == '/api/v1/accounts'
+ end
+
+ # Throttle paging, as it is mainly used for public pages and AP collections
+ throttle('throttle_authenticated_paging', limit: 300, period: 15.minutes) do |req|
+ req.authenticated_user_id if req.paging_request?
+ end
+
+ throttle('throttle_unauthenticated_paging', limit: 300, period: 15.minutes) do |req|
+ req.remote_ip if req.paging_request? && req.unauthenticated?
end
API_DELETE_REBLOG_REGEX = /\A\/api\/v1\/statuses\/[\d]+\/unreblog/.freeze
@@ -73,7 +90,7 @@ class Rack::Attack
end
throttle('protected_paths', limit: 25, period: 5.minutes) do |req|
- req.ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX
+ req.remote_ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX
end
self.throttled_response = lambda do |env|
diff --git a/config/locales/simple_form.co.yml b/config/locales/simple_form.co.yml
index 3a521e85e57..c4529093ba1 100644
--- a/config/locales/simple_form.co.yml
+++ b/config/locales/simple_form.co.yml
@@ -26,6 +26,7 @@ co:
password: Ci volenu almenu 8 caratteri
phrase: Sarà trovu senza primura di e maiuscule o di l'avertimenti
scopes: L'API à quelle l'applicazione averà accessu. S'è voi selezziunate un parametru d'altu livellu, un c'hè micca bisognu di selezziunà quell'individuali.
+ setting_advanced_layout: L'interfaccia avanzata cunsiste in parechje culonne persunalizabile
setting_aggregate_reblogs: Ùn mustrà micca e nove spartere per i statuti chì sò stati spartuti da pocu (tocca solu e spartere più ricente)
setting_default_language: A lingua di i vostri statuti pò esse induvinata autumaticamente, mà ùn marchja micca sempre bè
setting_display_media_default: Piattà i media marcati cum'è sensibili
@@ -90,6 +91,7 @@ co:
otp_attempt: Codice d’identificazione à dui fattori
password: Chjave d’accessu
phrase: Parolla-chjave o frasa
+ setting_advanced_layout: Attivà l'interfaccia web avanzata
setting_aggregate_reblogs: Gruppà e spartere indè e linee
setting_auto_play_gif: Lettura autumatica di i GIF animati
setting_boost_modal: Mustrà una cunfirmazione per sparte un statutu