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