diff --git a/.circleci/config.yml b/.circleci/config.yml index a373d685e..fabb6967e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,5 +221,5 @@ workflows: pkg-manager: yarn requires: - build - version: '16.18' + version: '16.19' yarn-run: test:jest diff --git a/.editorconfig b/.editorconfig index 5f8702cf8..b5217da4a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,4 @@ insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 +trim_trailing_whitespace = true diff --git a/.eslintrc.js b/.eslintrc.js index ca7fc83eb..4d81aa47e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,7 @@ module.exports = { 'eslint:recommended', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended', + 'plugin:import/recommended', ], env: { @@ -98,7 +99,6 @@ module.exports = { ignoreRestSiblings: true, }, ], - 'no-useless-escape': 'off', 'object-curly-spacing': ['error', 'always'], 'padded-blocks': [ 'error', @@ -178,6 +178,7 @@ module.exports = { }, ], + // See https://github.com/import-js/eslint-plugin-import/blob/main/config/recommended.js 'import/extensions': [ 'error', 'always', @@ -196,7 +197,6 @@ module.exports = { ], }, ], - 'import/no-unresolved': 'error', 'import/no-webpack-loader-syntax': 'error', 'promise/catch-or-return': [ diff --git a/.nvmrc b/.nvmrc index b6a7d89c6..030fcd56b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +16.19 diff --git a/.prettierignore b/.prettierignore index dc8c53693..d04207d39 100644 --- a/.prettierignore +++ b/.prettierignore @@ -71,6 +71,9 @@ docker-compose.override.yml /app/javascript/mastodon/locales /config/locales +# Ignore vendored CSS reset +app/javascript/styles/mastodon/reset.scss + # Ignore glitch-soc locale files /app/javascript/flavours/glitch/locales /config/locales-glitch diff --git a/.ruby-version b/.ruby-version index b0f2dcb32..eca690e73 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.4 +3.0.5 diff --git a/Dockerfile b/Dockerfile index ce7f4d718..04e3b58b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # syntax=docker/dockerfile:1.4 # This needs to be bullseye-slim because the Ruby image is built on bullseye-slim -ARG NODE_VERSION="16.18.1-bullseye-slim" +ARG NODE_VERSION="16.19-bullseye-slim" -FROM ghcr.io/moritzheiber/ruby-jemalloc:3.0.4-slim as ruby +FROM ghcr.io/moritzheiber/ruby-jemalloc:3.0.5-slim as ruby FROM node:${NODE_VERSION} as build COPY --link --from=ruby /opt/ruby /opt/ruby @@ -37,7 +37,8 @@ RUN apt-get update && \ bundle config set --local without 'development test' && \ bundle config set silence_root_warning true && \ bundle install -j"$(nproc)" && \ - yarn install --pure-lockfile --network-timeout 600000 + yarn install --pure-lockfile --network-timeout 600000 && \ + yarn cache clean FROM node:${NODE_VERSION} @@ -91,8 +92,7 @@ USER mastodon WORKDIR /opt/mastodon # Precompile assets -RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \ - yarn cache clean +RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile # Set the work dir and the container entry point ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/Gemfile b/Gemfile index 1ce7732e3..a3d2a9bde 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' gem 'rack', '~> 2.2.6' -gem 'hamlit-rails', '~> 0.2' +gem 'haml-rails', '~>2.0' gem 'pg', '~> 1.4' gem 'makara', '~> 0.5' gem 'pghero' @@ -40,7 +40,7 @@ end gem 'net-ldap', '~> 0.17' gem 'omniauth-cas', '~> 2.0' gem 'omniauth-saml', '~> 1.10' -gem 'gitlab-omniauth-openid-connect', '~>0.10.1', require: 'omniauth_openid_connect' +gem 'omniauth_openid_connect', '~> 0.6.0' gem 'omniauth', '~> 1.9' gem 'omniauth-rails_csrf_protection', '~> 0.1' @@ -81,7 +81,7 @@ gem 'ruby-progressbar', '~> 1.11' gem 'sanitize', '~> 6.0' gem 'scenic', '~> 1.7' gem 'sidekiq', '~> 6.5' -gem 'sidekiq-scheduler', '~> 4.0' +gem 'sidekiq-scheduler', '~> 5.0' gem 'sidekiq-unique-jobs', '~> 7.1' gem 'sidekiq-bulk', '~> 0.2.0' gem 'simple-navigation', '~> 4.4' @@ -122,7 +122,7 @@ group :test do gem 'climate_control', '~> 0.2' gem 'faker', '~> 3.1' gem 'json-schema', '~> 3.0' - gem 'rack-test', '~> 2.0' + gem 'rack-test', '~> 2.0' gem 'rails-controller-testing', '~> 1.0' gem 'rspec_junit_formatter', '~> 0.6' gem 'rspec-sidekiq', '~> 3.1' @@ -160,3 +160,5 @@ gem 'xorcist', '~> 1.1' gem 'hcaptcha', '~> 7.1' gem 'cocoon', '~> 1.2' + +gem 'net-http', '~> 0.3.2' diff --git a/Gemfile.lock b/Gemfile.lock index ab8c48c2f..0ad3d6328 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,8 +120,7 @@ GEM bindata (2.4.14) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - blurhash (0.1.6) - ffi (~> 1.14) + blurhash (0.1.7) bootsnap (1.16.0) msgpack (~> 1.2) brakeman (5.4.0) @@ -165,7 +164,7 @@ GEM activesupport cbor (0.5.9.6) charlock_holmes (0.7.7) - chewy (7.2.4) + chewy (7.2.7) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl @@ -231,7 +230,7 @@ GEM fabrication (2.30.0) faker (3.1.1) i18n (>= 1.8.11, < 2) - faraday (1.9.3) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -247,8 +246,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -273,27 +272,23 @@ GEM fog-json (>= 1.0) ipaddress (>= 0.8) formatador (0.3.0) - fugit (1.7.1) + fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) fuubar (2.5.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - gitlab-omniauth-openid-connect (0.10.1) - addressable (~> 2.7) - omniauth (>= 1.9, < 3) - openid_connect (~> 1.2) globalid (1.1.0) activesupport (>= 5.0) - hamlit (2.13.0) + haml (6.1.1) temple (>= 0.8.2) thor tilt - hamlit-rails (0.2.3) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - hamlit (>= 1.2.0) - railties (>= 4.0.1) + haml-rails (2.1.0) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6) + railties (>= 5.1) hashdiff (1.0.1) hashie (5.0.0) hcaptcha (7.1.0) @@ -412,7 +407,9 @@ GEM minitest (5.17.0) msgpack (1.6.0) multi_json (1.15.0) - multipart-post (2.1.1) + multipart-post (2.3.0) + net-http (0.3.2) + uri net-imap (0.3.4) date net-protocol @@ -449,6 +446,9 @@ GEM omniauth-saml (1.10.3) omniauth (~> 1.3, >= 1.3.2) ruby-saml (~> 1.9) + omniauth_openid_connect (0.6.0) + omniauth (>= 1.9, < 3) + openid_connect (~> 1.1) openid_connect (1.4.2) activemodel attr_required (>= 1.0.0) @@ -466,7 +466,7 @@ GEM orm_adapter (0.5.0) ox (2.14.14) parallel (1.22.1) - parser (3.2.0.0) + parser (3.2.1.0) ast (~> 2.4.1) parslet (2.0.0) pastel (0.8.0) @@ -562,7 +562,7 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.6.2) + regexp_parser (2.7.0) request_store (1.5.1) rack (>= 1.4) responders (3.0.1) @@ -597,7 +597,7 @@ GEM rspec-support (3.11.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.44.1) + rubocop (1.45.1) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) @@ -643,10 +643,9 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (4.0.3) - redis (>= 4.2.0) + sidekiq-scheduler (5.0.1) rufus-scheduler (~> 3.2) - sidekiq (>= 4, < 7) + sidekiq (>= 4, < 8) tilt (>= 1.4.0) sidekiq-unique-jobs (7.1.29) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) @@ -686,7 +685,7 @@ GEM activesupport (>= 3) attr_required (>= 0.0.5) httpclient (>= 2.4) - temple (0.8.2) + temple (0.10.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) terrapin (0.6.0) @@ -720,6 +719,7 @@ GEM unf_ext (0.0.8.2) unicode-display_width (2.4.2) uniform_notifier (1.16.0) + uri (0.12.0) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -756,7 +756,7 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.7) PLATFORMS ruby @@ -801,8 +801,7 @@ DEPENDENCIES fog-core (<= 2.4.0) fog-openstack (~> 0.3) fuubar (~> 2.5) - gitlab-omniauth-openid-connect (~> 0.10.1) - hamlit-rails (~> 0.2) + haml-rails (~> 2.0) hcaptcha (~> 7.1) hiredis (~> 0.6) htmlentities (~> 4.3) @@ -824,6 +823,7 @@ DEPENDENCIES mario-redis-lock (~> 1.2) memory_profiler mime-types (~> 3.4.1) + net-http (~> 0.3.2) net-ldap (~> 0.17) nokogiri (~> 1.14) nsa (~> 0.2) @@ -832,6 +832,7 @@ DEPENDENCIES omniauth-cas (~> 2.0) omniauth-rails_csrf_protection (~> 0.1) omniauth-saml (~> 1.10) + omniauth_openid_connect (~> 0.6.0) ox (~> 2.14) parslet pg (~> 1.4) @@ -871,7 +872,7 @@ DEPENDENCIES scenic (~> 1.7) sidekiq (~> 6.5) sidekiq-bulk (~> 0.2.0) - sidekiq-scheduler (~> 4.0) + sidekiq-scheduler (~> 5.0) sidekiq-unique-jobs (~> 7.1) simple-navigation (~> 4.4) simple_form (~> 5.2) diff --git a/SECURITY.md b/SECURITY.md index ccc7c1034..234172999 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through ## Supported Versions | Version | Supported | -| ------- | ----------| +| ------- | --------- | +| 4.1.x | Yes | | 4.0.x | Yes | | 3.5.x | Yes | | < 3.5 | No | diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 3a9cf056b..8dcf6331e 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -64,11 +64,18 @@ class Api::V1::StatusesController < Api::BaseController application: doorkeeper_token.application, poll: status_params[:poll], content_type: status_params[:content_type], + allowed_mentions: status_params[:allowed_mentions], idempotency: request.headers['Idempotency-Key'], with_rate_limit: true ) render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer + rescue PostStatusService::UnexpectedMentionsError => e + unexpected_accounts = ActiveModel::Serializer::CollectionSerializer.new( + e.accounts, + serializer: REST::AccountSerializer + ) + render json: { error: e.message, unexpected_accounts: unexpected_accounts }, status: 422 end def update @@ -131,6 +138,7 @@ class Api::V1::StatusesController < Api::BaseController :language, :scheduled_at, :content_type, + allowed_mentions: [], media_ids: [], media_attributes: [ :id, diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index b0f42b6a2..b491f85c2 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -8,7 +8,7 @@ import { me } from '../../initial_state'; const urlBase64ToUint8Array = (base64String) => { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) - .replace(/\-/g, '+') + .replace(/-/g, '+') .replace(/_/g, '/'); return decodeBase64(base64); diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 6b8922608..f02910f5a 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -511,7 +511,7 @@ class Status extends ImmutablePureComponent {
- + {status.get('edited_at') && *} diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js index 80e164af8..d6d60ebda 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -130,7 +130,7 @@ export default class MediaItem extends ImmutablePureComponent { return (
- + ({ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js index 571d1d838..3c6ed483d 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.js +++ b/app/javascript/mastodon/features/compose/containers/warning_container.js @@ -24,7 +24,7 @@ const buildHashtagRE = () => { '))', 'iu', ); } catch { - return /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i; + return /(?:^|[^/)\w])#(\w*[a-zA-Z·]\w*)/i; } }; diff --git a/app/javascript/mastodon/features/compose/util/counter.js b/app/javascript/mastodon/features/compose/util/counter.js index 5a68bad99..ec2431096 100644 --- a/app/javascript/mastodon/features/compose/util/counter.js +++ b/app/javascript/mastodon/features/compose/util/counter.js @@ -5,5 +5,5 @@ const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx'; export function countableText(inputText) { return inputText .replace(urlRegex, urlPlaceholder) - .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3'); + .replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '$1@$3'); } diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js index 571907a50..be793526d 100644 --- a/app/javascript/mastodon/features/emoji/emoji_utils.js +++ b/app/javascript/mastodon/features/emoji/emoji_utils.js @@ -73,7 +73,7 @@ const stringFromCodePoint = _String.fromCodePoint || function () { const _JSON = JSON; -const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; +const COLONS_REGEX = /^(?::([^:]+):)(?::skin-tone-(\d):)?$/; const SKINS = [ '1F3FA', '1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF', diff --git a/app/javascript/mastodon/features/follow_recommendations/components/account.js b/app/javascript/mastodon/features/follow_recommendations/components/account.js index daaa2f99e..ddd0c8baa 100644 --- a/app/javascript/mastodon/features/follow_recommendations/components/account.js +++ b/app/javascript/mastodon/features/follow_recommendations/components/account.js @@ -27,7 +27,7 @@ const makeMapStateToProps = () => { }; const getFirstSentence = str => { - const arr = str.split(/(([\.\?!]+\s)|[.。?!\n•])/); + const arr = str.split(/(([.?!]+\s)|[.。?!\n•])/); return arr[0]; }; diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js index d4afbabe3..0cae0bd1f 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.js +++ b/app/javascript/mastodon/features/getting_started/components/announcements.js @@ -6,9 +6,8 @@ import PropTypes from 'prop-types'; import IconButton from 'mastodon/components/icon_button'; import Icon from 'mastodon/components/icon'; import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; -import { autoPlayGif, reduceMotion, disableSwiping } from 'mastodon/initial_state'; +import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state'; import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; -import { mascot } from 'mastodon/initial_state'; import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light'; import classNames from 'classnames'; import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container'; diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index 9a70bd4f3..515afaca9 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -2,8 +2,7 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import ColumnSettings from '../components/column_settings'; import { changeSetting } from '../../../actions/settings'; -import { setFilter } from '../../../actions/notifications'; -import { clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; +import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { openModal } from '../../../actions/modal'; import { showAlert } from '../../../actions/alerts'; diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.js b/app/javascript/mastodon/features/picture_in_picture/components/footer.js index 3f59b891b..0ee6d06c7 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.js +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.js @@ -184,7 +184,7 @@ class Footer extends ImmutablePureComponent { - {withOpenButton && } + {withOpenButton && }
); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 116d9f6b2..064231ffe 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -276,7 +276,7 @@ class DetailedStatus extends ImmutablePureComponent { {media}
- + {edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 38bbc6895..2c6728fc0 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -5,7 +5,17 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { createSelector } from 'reselect'; -import { fetchStatus } from '../../actions/statuses'; +import { + fetchStatus, + muteStatus, + unmuteStatus, + deleteStatus, + editStatus, + hideStatus, + revealStatus, + translateStatus, + undoStatusTranslation, +} from '../../actions/statuses'; import MissingIndicator from '../../components/missing_indicator'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import DetailedStatus from './components/detailed_status'; @@ -26,16 +36,6 @@ import { mentionCompose, directCompose, } from '../../actions/compose'; -import { - muteStatus, - unmuteStatus, - deleteStatus, - editStatus, - hideStatus, - revealStatus, - translateStatus, - undoStatusTranslation, -} from '../../actions/statuses'; import { unblockAccount, unmuteAccount, diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index 087eadba2..d6a6cea31 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -98,7 +98,7 @@ class BoostModal extends ImmutablePureComponent {
- + diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 783d748ae..842b7af51 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -186,11 +186,12 @@ const ignoreSuggestion = (state, position, token, completion, path) => { }; const sortHashtagsByUse = (state, tags) => { - const personalHistory = state.get('tagHistory'); + const personalHistory = state.get('tagHistory').map(tag => tag.toLowerCase()); - return tags.sort((a, b) => { - const usedA = personalHistory.includes(a.name); - const usedB = personalHistory.includes(b.name); + const tagsWithLowercase = tags.map(t => ({ ...t, lowerName: t.name.toLowerCase() })); + const sorted = tagsWithLowercase.sort((a, b) => { + const usedA = personalHistory.includes(a.lowerName); + const usedB = personalHistory.includes(b.lowerName); if (usedA === usedB) { return 0; @@ -200,6 +201,8 @@ const sortHashtagsByUse = (state, tags) => { return 1; } }); + sorted.forEach(tag => delete tag.lowerName); + return sorted; }; const insertEmoji = (state, position, emojiData, needsSpace) => { diff --git a/app/javascript/styles/fonts/roboto-mono.scss b/app/javascript/styles/fonts/roboto-mono.scss index 909d1a13e..66f3eed9f 100644 --- a/app/javascript/styles/fonts/roboto-mono.scss +++ b/app/javascript/styles/fonts/roboto-mono.scss @@ -1,11 +1,12 @@ @font-face { font-family: mastodon-font-monospace; - src: - local('Roboto Mono'), + src: local('Roboto Mono'), url('~fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'), url('~fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'), - url('~fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'), - url('~fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg'); + url('~fonts/roboto-mono/robotomono-regular-webfont.ttf') + format('truetype'), + url('~fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') + format('svg'); font-weight: 400; font-display: swap; font-style: normal; diff --git a/app/javascript/styles/fonts/roboto.scss b/app/javascript/styles/fonts/roboto.scss index 0ccc43094..07cf0cb00 100644 --- a/app/javascript/styles/fonts/roboto.scss +++ b/app/javascript/styles/fonts/roboto.scss @@ -1,11 +1,11 @@ @font-face { font-family: mastodon-font-sans-serif; - src: - local('Roboto Italic'), + src: local('Roboto Italic'), url('~fonts/roboto/roboto-italic-webfont.woff2') format('woff2'), url('~fonts/roboto/roboto-italic-webfont.woff') format('woff'), url('~fonts/roboto/roboto-italic-webfont.ttf') format('truetype'), - url('~fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg'); + url('~fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') + format('svg'); font-weight: normal; font-display: swap; font-style: italic; @@ -13,12 +13,12 @@ @font-face { font-family: mastodon-font-sans-serif; - src: - local('Roboto Bold'), + src: local('Roboto Bold'), url('~fonts/roboto/roboto-bold-webfont.woff2') format('woff2'), url('~fonts/roboto/roboto-bold-webfont.woff') format('woff'), url('~fonts/roboto/roboto-bold-webfont.ttf') format('truetype'), - url('~fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg'); + url('~fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') + format('svg'); font-weight: bold; font-display: swap; font-style: normal; @@ -26,12 +26,12 @@ @font-face { font-family: mastodon-font-sans-serif; - src: - local('Roboto Medium'), + src: local('Roboto Medium'), url('~fonts/roboto/roboto-medium-webfont.woff2') format('woff2'), url('~fonts/roboto/roboto-medium-webfont.woff') format('woff'), url('~fonts/roboto/roboto-medium-webfont.ttf') format('truetype'), - url('~fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg'); + url('~fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') + format('svg'); font-weight: 500; font-display: swap; font-style: normal; @@ -39,12 +39,12 @@ @font-face { font-family: mastodon-font-sans-serif; - src: - local('Roboto'), + src: local('Roboto'), url('~fonts/roboto/roboto-regular-webfont.woff2') format('woff2'), url('~fonts/roboto/roboto-regular-webfont.woff') format('woff'), url('~fonts/roboto/roboto-regular-webfont.ttf') format('truetype'), - url('~fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg'); + url('~fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') + format('svg'); font-weight: normal; font-display: swap; font-style: normal; diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index c37100a28..01725cf96 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -152,7 +152,7 @@ html { } .compose-form__autosuggest-wrapper, -.poll__option input[type="text"], +.poll__option input[type='text'], .compose-form .spoiler-input__input, .compose-form__poll-wrapper select, .search__input, @@ -179,7 +179,9 @@ html { } .compose-form__poll-wrapper select { - background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; + background: $simple-background-color + url("data:image/svg+xml;utf8,") + no-repeat right 8px center / auto 16px; } .compose-form__poll-wrapper, @@ -205,7 +207,9 @@ html { } .drawer__inner__mastodon { - background: $white url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; + background: $white + url('data:image/svg+xml;utf8,') + no-repeat bottom / 100% auto; } // Change the colors used in compose-form @@ -332,11 +336,13 @@ html { color: $white; } -.language-dropdown__dropdown__results__item .language-dropdown__dropdown__results__item__common-name { +.language-dropdown__dropdown__results__item + .language-dropdown__dropdown__results__item__common-name { color: lighten($ui-base-color, 8%); } -.language-dropdown__dropdown__results__item.active .language-dropdown__dropdown__results__item__common-name { +.language-dropdown__dropdown__results__item.active + .language-dropdown__dropdown__results__item__common-name { color: darken($ui-base-color, 12%); } @@ -490,7 +496,8 @@ html { background: darken($ui-secondary-color, 10%); } -.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { +.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) + .react-toggle-track { background: lighten($ui-highlight-color, 10%); } @@ -522,10 +529,10 @@ html { } .simple_form { - input[type="text"], - input[type="number"], - input[type="email"], - input[type="password"], + input[type='text'], + input[type='number'], + input[type='email'], + input[type='password'], textarea { &:hover { border-color: lighten($ui-base-color, 12%); @@ -682,5 +689,7 @@ html { .mute-modal select { border: 1px solid lighten($ui-base-color, 8%); - background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; + background: $simple-background-color + url("data:image/svg+xml;utf8,") + no-repeat right 8px center / auto 16px; } diff --git a/app/javascript/styles/mastodon/accessibility.scss b/app/javascript/styles/mastodon/accessibility.scss index c5bcb5941..deaa0afda 100644 --- a/app/javascript/styles/mastodon/accessibility.scss +++ b/app/javascript/styles/mastodon/accessibility.scss @@ -1,4 +1,7 @@ -$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange' 'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign' 'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on' 'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default; +$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange' + 'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign' + 'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on' + 'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default; %emoji-color-inversion { filter: invert(1); diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 798d520cd..08a79c11e 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -1,4 +1,4 @@ -@use "sass:math"; +@use 'sass:math'; $no-columns-breakpoint: 600px; $sidebar-width: 240px; @@ -1147,7 +1147,10 @@ a.name-tag, @for $i from 0 through 10 { &--#{10 * $i} { - background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10))); + background-color: rgba( + $ui-highlight-color, + 1 * (math.div(max(1, $i), 10)) + ); } } } @@ -1236,7 +1239,12 @@ a.sparkline { .skeleton { background-color: lighten($ui-base-color, 8%); - background-image: linear-gradient(90deg, lighten($ui-base-color, 8%), lighten($ui-base-color, 12%), lighten($ui-base-color, 8%)); + background-image: linear-gradient( + 90deg, + lighten($ui-base-color, 8%), + lighten($ui-base-color, 12%), + lighten($ui-base-color, 8%) + ); background-size: 200px 100%; background-repeat: no-repeat; border-radius: 4px; @@ -1285,7 +1293,10 @@ a.sparkline { @for $i from 0 through 10 { &--#{10 * $i} { - background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10))); + background-color: rgba( + $ui-highlight-color, + 1 * (math.div(max(1, $i), 10)) + ); } } } @@ -1431,7 +1442,7 @@ a.sparkline { &::after { display: block; - content: ""; + content: ''; width: 50px; height: 21px; position: absolute; @@ -1825,7 +1836,7 @@ a.sparkline { &::after { position: absolute; - content: ""; + content: ''; width: 1px; background: $highlight-text-color; bottom: 0; diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 413a1cdd6..1d08b12e5 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -14,7 +14,7 @@ body { font-weight: 400; color: $primary-text-color; text-rendering: optimizelegibility; - font-feature-settings: "kern"; + font-feature-settings: 'kern'; text-size-adjust: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0%); -webkit-tap-highlight-color: transparent; @@ -31,7 +31,9 @@ body { // Droid Sans => Older Androids (<4.0) // Helvetica Neue => Older macOS <10.11 // $font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0) - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", $font-sans-serif, sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + $font-sans-serif, sans-serif; } &.app-body { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 25cbbf000..dc1153073 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -387,7 +387,7 @@ body > [data-popper-placement] { .ellipsis { &::after { - content: "…"; + content: '…'; } } @@ -404,7 +404,7 @@ body > [data-popper-placement] { color: $highlight-text-color; } - input[type="checkbox"] { + input[type='checkbox'] { display: none; } @@ -423,7 +423,9 @@ body > [data-popper-placement] { &.active { border-color: $highlight-text-color; - background: $highlight-text-color url("data:image/svg+xml;utf8,") center center no-repeat; + background: $highlight-text-color + url("data:image/svg+xml;utf8,") + center center no-repeat; } } } @@ -647,7 +649,12 @@ body > [data-popper-placement] { margin: 5px; &__actions { - background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); + background: linear-gradient( + 180deg, + rgba($base-shadow-color, 0.8) 0, + rgba($base-shadow-color, 0.35) 80%, + transparent + ); display: flex; align-items: flex-start; justify-content: space-between; @@ -675,7 +682,12 @@ body > [data-popper-placement] { left: 0; right: 0; box-sizing: border-box; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); + background: linear-gradient( + 0deg, + rgba($base-shadow-color, 0.8) 0, + rgba($base-shadow-color, 0.35) 80%, + transparent + ); } } @@ -1080,8 +1092,13 @@ body > [data-popper-placement] { cursor: auto; @keyframes fade { - 0% { opacity: 0; } - 100% { opacity: 1; } + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } } opacity: 1; @@ -1827,11 +1844,11 @@ a.account__display-name { justify-content: center; flex-direction: column; scrollbar-width: none; /* Firefox */ - -ms-overflow-style: none; /* IE 10+ */ + -ms-overflow-style: none; /* IE 10+ */ * { scrollbar-width: none; /* Firefox */ - -ms-overflow-style: none; /* IE 10+ */ + -ms-overflow-style: none; /* IE 10+ */ } &::-webkit-scrollbar, @@ -2863,7 +2880,9 @@ $ui-header-height: 55px; } .drawer__inner__mastodon { - background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,') no-repeat bottom / 100% auto; + background: lighten($ui-base-color, 13%) + url('data:image/svg+xml;utf8,') + no-repeat bottom / 100% auto; flex: 1; min-height: 47px; display: none; @@ -2918,7 +2937,8 @@ $ui-header-height: 55px; overflow-y: auto; } - @supports (display: grid) { // hack to fix Chrome <57 + @supports (display: grid) { + // hack to fix Chrome <57 contain: strict; } @@ -2939,7 +2959,8 @@ $ui-header-height: 55px; } .scrollable.fullscreen { - @supports (display: grid) { // hack to fix Chrome <57 + @supports (display: grid) { + // hack to fix Chrome <57 contain: none; } } @@ -3043,7 +3064,8 @@ $ui-header-height: 55px; transition: background-color 0.2s ease; } -.react-toggle:is(:hover, :focus-within):not(.react-toggle--disabled) .react-toggle-track { +.react-toggle:is(:hover, :focus-within):not(.react-toggle--disabled) + .react-toggle-track { background-color: darken($ui-base-color, 10%); } @@ -3051,7 +3073,8 @@ $ui-header-height: 55px; background-color: darken($ui-highlight-color, 2%); } -.react-toggle--checked:is(:hover, :focus-within):not(.react-toggle--disabled) .react-toggle-track { +.react-toggle--checked:is(:hover, :focus-within):not(.react-toggle--disabled) + .react-toggle-track { background-color: $ui-highlight-color; } @@ -3646,7 +3669,7 @@ a.status-card.compact:hover { &::before { display: block; - content: ""; + content: ''; position: absolute; bottom: -13px; left: 0; @@ -3656,7 +3679,11 @@ a.status-card.compact:hover { pointer-events: none; height: 28px; z-index: 1; - background: radial-gradient(ellipse, rgba($ui-highlight-color, 0.23) 0%, rgba($ui-highlight-color, 0) 60%); + background: radial-gradient( + ellipse, + rgba($ui-highlight-color, 0.23) 0%, + rgba($ui-highlight-color, 0) 60% + ); } } @@ -4241,7 +4268,8 @@ a.status-card.compact:hover { align-items: center; justify-content: center; - @supports (display: grid) { // hack to fix Chrome <57 + @supports (display: grid) { + // hack to fix Chrome <57 contain: strict; } @@ -5747,7 +5775,9 @@ a.status-card.compact:hover { width: auto; outline: 0; font-family: inherit; - background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; + background: $simple-background-color + url("data:image/svg+xml;utf8,") + no-repeat right 8px center / auto 16px; border: 1px solid darken($simple-background-color, 14%); border-radius: 4px; padding: 6px 10px; @@ -6154,7 +6184,12 @@ a.status-card.compact:hover { left: 0; right: 0; box-sizing: border-box; - background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent); + background: linear-gradient( + 0deg, + rgba($base-shadow-color, 0.85) 0, + rgba($base-shadow-color, 0.45) 60%, + transparent + ); padding: 0 15px; opacity: 0; transition: opacity 0.1s ease; @@ -6295,7 +6330,7 @@ a.status-card.compact:hover { } &::before { - content: ""; + content: ''; width: 50px; background: rgba($white, 0.35); border-radius: 4px; @@ -6365,7 +6400,7 @@ a.status-card.compact:hover { position: relative; &::before { - content: ""; + content: ''; width: 100%; background: rgba($white, 0.35); border-radius: 4px; @@ -6448,7 +6483,11 @@ a.status-card.compact:hover { } .scrollable .account-card__bio::after { - background: linear-gradient(to left, lighten($ui-base-color, 8%), transparent); + background: linear-gradient( + to left, + lighten($ui-base-color, 8%), + transparent + ); } .account-gallery__container { @@ -6509,7 +6548,7 @@ a.status-card.compact:hover { &::before, &::after { display: block; - content: ""; + content: ''; position: absolute; bottom: 0; left: 50%; @@ -6575,8 +6614,8 @@ a.status-card.compact:hover { text-overflow: ellipsis; cursor: pointer; - input[type="radio"], - input[type="checkbox"] { + input[type='radio'], + input[type='checkbox'] { display: none; } @@ -6635,9 +6674,17 @@ noscript { } @keyframes flicker { - 0% { opacity: 1; } - 30% { opacity: 0.75; } - 100% { opacity: 1; } + 0% { + opacity: 1; + } + + 30% { + opacity: 0.75; + } + + 100% { + opacity: 1; + } } @media screen and (max-width: 630px) and (max-height: 400px) { @@ -6658,7 +6705,9 @@ noscript { .navigation-bar { & > a:first-child { will-change: margin-top, margin-left, margin-right, width; - transition: margin-top $duration $delay, margin-left $duration ($duration + $delay), margin-right $duration ($duration + $delay); + transition: margin-top $duration $delay, + margin-left $duration ($duration + $delay), + margin-right $duration ($duration + $delay); } & > .navigation-bar__profile-edit { @@ -6669,15 +6718,12 @@ noscript { .navigation-bar__actions { & > .icon-button.close { will-change: opacity transform; - transition: - opacity $duration * 0.5 $delay, - transform $duration $delay; + transition: opacity $duration * 0.5 $delay, transform $duration $delay; } & > .compose__action-bar .icon-button { will-change: opacity transform; - transition: - opacity $duration * 0.5 $delay + $duration * 0.5, + transition: opacity $duration * 0.5 $delay + $duration * 0.5, transform $duration $delay; } } @@ -7677,7 +7723,11 @@ noscript { &.active { transition: all 100ms ease-in; transition-property: background-color, color; - background-color: mix(lighten($ui-base-color, 12%), $ui-highlight-color, 80%); + background-color: mix( + lighten($ui-base-color, 12%), + $ui-highlight-color, + 80% + ); .reactions-bar__item__count { color: lighten($highlight-text-color, 8%); @@ -7730,7 +7780,7 @@ noscript { &.unread { &::before { - content: ""; + content: ''; position: absolute; top: 0; left: 0; @@ -8258,14 +8308,14 @@ noscript { counter-increment: list-counter; &::before { - content: counter(list-counter) "."; + content: counter(list-counter) '.'; position: absolute; left: 0; } } ul > li::before { - content: ""; + content: ''; position: absolute; background-color: $darker-text-color; border-radius: 50%; diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 1042ddda8..0d7a7df2e 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -174,7 +174,7 @@ &:hover::before { z-index: -1; - content: ""; + content: ''; position: absolute; top: 0; left: 0; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 1841dc8bf..e4539deff 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -299,7 +299,7 @@ code { max-width: 100%; height: auto; border-radius: 4px; - background: url("images/void.png"); + background: url('images/void.png'); &:last-child { margin-bottom: 0; @@ -384,7 +384,7 @@ code { flex: 1 1 auto; } - input[type="checkbox"] { + input[type='checkbox'] { position: absolute; left: 0; top: 5px; @@ -400,12 +400,12 @@ code { border-radius: 4px; } - input[type="text"], - input[type="number"], - input[type="email"], - input[type="password"], - input[type="url"], - input[type="datetime-local"], + input[type='text'], + input[type='number'], + input[type='email'], + input[type='password'], + input[type='url'], + input[type='datetime-local'], textarea { box-sizing: border-box; font-size: 16px; @@ -443,11 +443,11 @@ code { } } - input[type="text"], - input[type="number"], - input[type="email"], - input[type="password"], - input[type="datetime-local"] { + input[type='text'], + input[type='number'], + input[type='email'], + input[type='password'], + input[type='datetime-local'] { &:focus:invalid:not(:placeholder-shown), &:required:invalid:not(:placeholder-shown) { border-color: lighten($error-red, 12%); @@ -459,11 +459,11 @@ code { color: lighten($error-red, 12%); } - input[type="text"], - input[type="number"], - input[type="email"], - input[type="password"], - input[type="datetime-local"], + input[type='text'], + input[type='number'], + input[type='email'], + input[type='password'], + input[type='datetime-local'], textarea, select { border-color: lighten($error-red, 12%); @@ -567,7 +567,9 @@ code { outline: 0; font-family: inherit; resize: vertical; - background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; + background: darken($ui-base-color, 10%) + url("data:image/svg+xml;utf8,") + no-repeat right 8px center / auto 16px; border: 1px solid darken($ui-base-color, 14%); border-radius: 4px; padding-left: 10px; @@ -607,7 +609,11 @@ code { right: 0; bottom: 1px; width: 5px; - background-image: linear-gradient(to right, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%)); + background-image: linear-gradient( + to right, + rgba(darken($ui-base-color, 10%), 0), + darken($ui-base-color, 10%) + ); } } } @@ -995,7 +1001,7 @@ code { flex: 1 1 auto; } - input[type="text"] { + input[type='text'] { background: transparent; border: 0; padding: 10px; diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss index a333926dd..6170877b2 100644 --- a/app/javascript/styles/mastodon/modal.scss +++ b/app/javascript/styles/mastodon/modal.scss @@ -1,5 +1,7 @@ .modal-layout { - background: $ui-base-color url('data:image/svg+xml;utf8,') repeat-x bottom fixed; + background: $ui-base-color + url('data:image/svg+xml;utf8,') + repeat-x bottom fixed; display: flex; flex-direction: column; height: 100vh; diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 03d2cb4b2..b30932e04 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -64,8 +64,8 @@ max-width: calc(100% - 45px - 25px); } - input[type="radio"], - input[type="checkbox"] { + input[type='radio'], + input[type='checkbox'] { display: none; } @@ -73,7 +73,7 @@ flex: 1 1 auto; } - input[type="text"] { + input[type='text'] { display: block; box-sizing: border-box; width: 100%; @@ -263,7 +263,9 @@ width: auto; outline: 0; font-family: inherit; - background: $simple-background-color url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; + background: $simple-background-color + url("data:image/svg+xml;utf8,") + no-repeat right 8px center / auto 16px; border: 1px solid darken($simple-background-color, 14%); border-radius: 4px; padding: 6px 10px; diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss index 39f4653bb..e60087dab 100644 --- a/app/javascript/styles/mastodon/rtl.scss +++ b/app/javascript/styles/mastodon/rtl.scss @@ -229,8 +229,8 @@ body.rtl { padding-right: 0; } - .simple_form .check_boxes .checkbox input[type="checkbox"], - .simple_form .input.boolean input[type="checkbox"] { + .simple_form .check_boxes .checkbox input[type='checkbox'], + .simple_form .input.boolean input[type='checkbox'] { left: auto; right: 0; } @@ -268,12 +268,18 @@ body.rtl { &::after { right: auto; left: 0; - background-image: linear-gradient(to left, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%)); + background-image: linear-gradient( + to left, + rgba(darken($ui-base-color, 10%), 0), + darken($ui-base-color, 10%) + ); } } .simple_form select { - background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,") no-repeat left 8px center / auto 16px; + background: darken($ui-base-color, 10%) + url("data:image/svg+xml;utf8,") + no-repeat left 8px center / auto 16px; } .table th, @@ -320,11 +326,11 @@ body.rtl { } .fa-chevron-left::before { - content: "\F054"; + content: '\F054'; } .fa-chevron-right::before { - content: "\F053"; + content: '\F053'; } .column-back-button__icon { diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss index ce71d11e4..a42f1f42c 100644 --- a/app/javascript/styles/mastodon/statuses.scss +++ b/app/javascript/styles/mastodon/statuses.scss @@ -138,7 +138,7 @@ a.button.logo-button { } .embed { - .status__content[data-spoiler="folded"] { + .status__content[data-spoiler='folded'] { .e-content { display: none; } diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index 2f6c41d5f..7de25f8fd 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -1,18 +1,18 @@ // Commonly used web colors -$black: #000000; // Black -$white: #ffffff; // White -$success-green: #79bd9a !default; // Padua -$error-red: #df405a !default; // Cerise -$warning-red: #ff5050 !default; // Sunset Orange -$gold-star: #ca8f04 !default; // Dark Goldenrod +$black: #000000; // Black +$white: #ffffff; // White +$success-green: #79bd9a !default; // Padua +$error-red: #df405a !default; // Cerise +$warning-red: #ff5050 !default; // Sunset Orange +$gold-star: #ca8f04 !default; // Dark Goldenrod $red-bookmark: $warning-red; // Values from the classic Mastodon UI -$classic-base-color: #282c37; // Midnight Express -$classic-primary-color: #9baec8; // Echo Blue -$classic-secondary-color: #d9e1e8; // Pattens Blue -$classic-highlight-color: #6364ff; // Brand purple +$classic-base-color: #282c37; // Midnight Express +$classic-primary-color: #9baec8; // Echo Blue +$classic-secondary-color: #d9e1e8; // Pattens Blue +$classic-highlight-color: #6364ff; // Brand purple // Variables for defaults in UI $base-shadow-color: $black !default; @@ -23,10 +23,13 @@ $valid-value-color: $success-green !default; $error-value-color: $error-red !default; // Tell UI to use selected colors -$ui-base-color: $classic-base-color !default; // Darkest -$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest -$ui-primary-color: $classic-primary-color !default; // Lighter -$ui-secondary-color: $classic-secondary-color !default; // Lightest +$ui-base-color: $classic-base-color !default; // Darkest +$ui-base-lighter-color: lighten( + $ui-base-color, + 26% +) !default; // Lighter darkest +$ui-primary-color: $classic-primary-color !default; // Lighter +$ui-secondary-color: $classic-secondary-color !default; // Lightest $ui-highlight-color: $classic-highlight-color !default; // Variables for texts diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 7a25d121b..ef7bfc6de 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -1,4 +1,4 @@ -@use "sass:math"; +@use 'sass:math'; .hero-widget { margin-bottom: 10px; diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index bcda001f5..77527f2db 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -6,6 +6,15 @@ class PostStatusService < BaseService MIN_SCHEDULE_OFFSET = 5.minutes.freeze + class UnexpectedMentionsError < StandardError + attr_reader :accounts + + def initialize(message, accounts) + super(message) + @accounts = accounts + end + end + # Post a text status update, fetch and notify remote users mentioned # @param [Account] account Account from which to post # @param [Hash] options @@ -21,6 +30,7 @@ class PostStatusService < BaseService # @option [Doorkeeper::Application] :application # @option [String] :idempotency Optional idempotency key # @option [Boolean] :with_rate_limit + # @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions # @return [Status] def call(account, options = {}) @account = account @@ -72,14 +82,27 @@ class PostStatusService < BaseService end def process_status! + @status = @account.statuses.new(status_attributes) + process_mentions_service.call(@status, save_records: false) + safeguard_mentions!(@status) + # The following transaction block is needed to wrap the UPDATEs to # the media attachments when the status is created - ApplicationRecord.transaction do - @status = @account.statuses.create!(status_attributes) + @status.save! end end + def safeguard_mentions!(status) + return if @options[:allowed_mentions].nil? + expected_account_ids = @options[:allowed_mentions].map(&:to_i) + + unexpected_accounts = status.mentions.map(&:account).to_a.reject { |mentioned_account| expected_account_ids.include?(mentioned_account.id) } + return if unexpected_accounts.empty? + + raise UnexpectedMentionsError.new('Post would be sent to unexpected accounts', unexpected_accounts) + end + def schedule_status! status_for_validation = @account.statuses.build(status_attributes) @@ -102,7 +125,6 @@ class PostStatusService < BaseService def postprocess_status! process_hashtags_service.call(@status) - process_mentions_service.call(@status) Trends.tags.register(@status) LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index b117db8c2..93a96667e 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -3,12 +3,13 @@ class ProcessMentionsService < BaseService include Payloadable - # Scan status for mentions and fetch remote mentioned users, create - # local mention pointers, send Salmon notifications to mentioned - # remote users + # Scan status for mentions and fetch remote mentioned users, + # and create local mention pointers # @param [Status] status - def call(status) + # @param [Boolean] save_records Whether to save records in database + def call(status, save_records: true) @status = status + @save_records = save_records return unless @status.local? @@ -55,14 +56,15 @@ class ProcessMentionsService < BaseService next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended? mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id } - mention ||= mentioned_account.mentions.new(status: @status) + mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } + mention ||= @status.mentions.new(account: mentioned_account) @current_mentions << mention "@#{mentioned_account.acct}" end - @status.save! + @status.save! if @save_records end def assign_mentions! @@ -73,11 +75,12 @@ class ProcessMentionsService < BaseService mentioned_account_ids = @current_mentions.map(&:account_id) blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) - @current_mentions.select! { |mention| !(blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain)) } + dropped_mentions, @current_mentions = @current_mentions.partition { |mention| blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain) } + dropped_mentions.each(&:destroy) end @current_mentions.each do |mention| - mention.save if mention.new_record? + mention.save if mention.new_record? && @save_records end # If previous mentions are no longer contained in the text, convert them diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index db5c255c9..c8a9d33a7 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -37,7 +37,7 @@ .dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size') .dashboard__counters__label= t 'admin.accounts.media_attachments' %div - = link_to admin_account_relationships_path(@account.id, location: 'local', relationship: 'followed_by') do + = link_to admin_account_relationships_path(@account.id, location: @account.local? ? nil : 'local', relationship: 'followed_by') do .dashboard__counters__num= number_with_delimiter @account.local_followers_count .dashboard__counters__label= t 'admin.accounts.followers' %div diff --git a/config/sidekiq.yml b/config/sidekiq.yml index b8739aab3..2278329a5 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -9,52 +9,52 @@ - [scheduler] :scheduler: :listened_queues_only: true -:schedule: - scheduled_statuses_scheduler: - every: '5m' - class: Scheduler::ScheduledStatusesScheduler - queue: scheduler - trends_refresh_scheduler: - every: '5m' - class: Scheduler::Trends::RefreshScheduler - queue: scheduler - trends_review_notifications_scheduler: - every: '6h' - class: Scheduler::Trends::ReviewNotificationsScheduler - queue: scheduler - indexing_scheduler: - every: '5m' - class: Scheduler::IndexingScheduler - queue: scheduler - vacuum_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' - class: Scheduler::VacuumScheduler - queue: scheduler - follow_recommendations_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *' - class: Scheduler::FollowRecommendationsScheduler - queue: scheduler - user_cleanup_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *' - class: Scheduler::UserCleanupScheduler - queue: scheduler - ip_cleanup_scheduler: - cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' - class: Scheduler::IpCleanupScheduler - queue: scheduler - pghero_scheduler: - cron: '0 0 * * *' - class: Scheduler::PgheroScheduler - queue: scheduler - instance_refresh_scheduler: - cron: '0 * * * *' - class: Scheduler::InstanceRefreshScheduler - queue: scheduler - accounts_statuses_cleanup_scheduler: - interval: 1 minute - class: Scheduler::AccountsStatusesCleanupScheduler - queue: scheduler - suspended_user_cleanup_scheduler: - interval: 1 minute - class: Scheduler::SuspendedUserCleanupScheduler - queue: scheduler + :schedule: + scheduled_statuses_scheduler: + every: '5m' + class: Scheduler::ScheduledStatusesScheduler + queue: scheduler + trends_refresh_scheduler: + every: '5m' + class: Scheduler::Trends::RefreshScheduler + queue: scheduler + trends_review_notifications_scheduler: + every: '6h' + class: Scheduler::Trends::ReviewNotificationsScheduler + queue: scheduler + indexing_scheduler: + every: '5m' + class: Scheduler::IndexingScheduler + queue: scheduler + vacuum_scheduler: + cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' + class: Scheduler::VacuumScheduler + queue: scheduler + follow_recommendations_scheduler: + cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *' + class: Scheduler::FollowRecommendationsScheduler + queue: scheduler + user_cleanup_scheduler: + cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *' + class: Scheduler::UserCleanupScheduler + queue: scheduler + ip_cleanup_scheduler: + cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' + class: Scheduler::IpCleanupScheduler + queue: scheduler + pghero_scheduler: + cron: '0 0 * * *' + class: Scheduler::PgheroScheduler + queue: scheduler + instance_refresh_scheduler: + cron: '0 * * * *' + class: Scheduler::InstanceRefreshScheduler + queue: scheduler + accounts_statuses_cleanup_scheduler: + interval: 1 minute + class: Scheduler::AccountsStatusesCleanupScheduler + queue: scheduler + suspended_user_cleanup_scheduler: + interval: 1 minute + class: Scheduler::SuspendedUserCleanupScheduler + queue: scheduler diff --git a/config/webpack/shared.js b/config/webpack/shared.js index bbf9f51f1..405858d0c 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -81,7 +81,7 @@ module.exports = { }, minChunks: 2, minSize: 0, - test: /^(?!.*[\\\/]node_modules[\\\/]react-intl[\\\/]).+$/, + test: /^(?!.*[\\/]node_modules[\\/]react-intl[\\/]).+$/, }, }, }, diff --git a/lib/assets/wordmark.light.css b/lib/assets/wordmark.light.css index 9a601f972..b8c9993fd 100644 --- a/lib/assets/wordmark.light.css +++ b/lib/assets/wordmark.light.css @@ -1 +1,3 @@ -use { color: #000 !important; } +use { + color: #000 !important; +} diff --git a/package.json b/package.json index 193b908f6..5f3528cab 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:jest", "test:lint": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:lint:sass", "test:lint:js": "eslint --ext=js . --cache --report-unused-disable-directives", - "test:lint:sass": "stylelint \"**/*.{css,scss}\"", + "test:lint:sass": "stylelint \"**/*.{css,scss}\" && prettier --check \"**/*.{css,scss}\"", "test:jest": "cross-env NODE_ENV=test jest", "format": "prettier --write \"**/*.{json,yml}\"", "format-check": "prettier --check \"**/*.{json,yml}\"" @@ -159,8 +159,8 @@ "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", "react-test-renderer": "^16.14.0", - "stylelint": "^14.16.1", - "stylelint-config-standard-scss": "^6.1.0", + "stylelint": "^15.1.0", + "stylelint-config-standard-scss": "^7.0.0", "webpack-dev-server": "^3.11.3", "yargs": "^17.6.2" }, diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index b962b3398..01d745fc0 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'rails_helper' describe Api::V1::Accounts::StatusesController do @@ -15,7 +16,12 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(200) + expect(response).to have_http_status(:ok) + end + + it 'returns expected headers' do + get :index, params: { account_id: user.account.id, limit: 1 } + expect(response.headers['Link'].links.size).to eq(2) end @@ -23,19 +29,29 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, only_media: true } - expect(response).to have_http_status(200) + expect(response).to have_http_status(:ok) end end context 'with exclude replies' do + let!(:older_statuses) { user.account.statuses.destroy_all } + let!(:status) { Fabricate(:status, account: user.account) } + let!(:status_self_reply) { Fabricate(:status, account: user.account, thread: status) } + before do - Fabricate(:status, account: user.account, thread: Fabricate(:status)) + Fabricate(:status, account: user.account, thread: Fabricate(:status)) # Reply to another user + get :index, params: { account_id: user.account.id, exclude_replies: true } end it 'returns http success' do - get :index, params: { account_id: user.account.id, exclude_replies: true } + expect(response).to have_http_status(:ok) + end + + it 'returns posts along with self replies' do + json = body_as_json + post_ids = json.map { |item| item[:id].to_i }.sort - expect(response).to have_http_status(200) + expect(post_ids).to eq [status.id, status_self_reply.id] end end @@ -47,7 +63,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, pinned: true } - expect(response).to have_http_status(200) + expect(response).to have_http_status(:ok) end end @@ -55,12 +71,15 @@ describe Api::V1::Accounts::StatusesController do let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com') } let(:status) { Fabricate(:status, account: account) } let(:private_status) { Fabricate(:status, account: account, visibility: :private) } - let!(:pin) { Fabricate(:status_pin, account: account, status: status) } - let!(:private_pin) { Fabricate(:status_pin, account: account, status: private_status) } + + before do + Fabricate(:status_pin, account: account, status: status) + Fabricate(:status_pin, account: account, status: private_status) + end it 'returns http success' do get :index, params: { account_id: account.id, pinned: true } - expect(response).to have_http_status(200) + expect(response).to have_http_status(:ok) end context 'when user does not follow account' do diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index 24810a5d2..bd8b8013a 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -133,6 +133,23 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end end + context 'with a safeguard' do + let!(:alice) { Fabricate(:account, username: 'alice') } + let!(:bob) { Fabricate(:account, username: 'bob') } + + before do + post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] } + end + + it 'returns http unprocessable entity' do + expect(response).to have_http_status(422) + end + + it 'returns serialized extra accounts in body' do + expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }] + end + end + context 'with missing parameters' do before do post :create, params: {} diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb new file mode 100644 index 000000000..ce29df3a7 --- /dev/null +++ b/spec/serializers/rest/account_serializer_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::AccountSerializer do + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) } + let(:user) { Fabricate(:user, role: role) } + let(:account) { user.account} + + subject { JSON.parse(ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer).to_json) } + + context 'when the account is suspended' do + before do + account.suspend! + end + + it 'returns empty roles' do + expect(subject['roles']).to eq [] + end + end + + context 'when the account has a highlighted role' do + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) } + + it 'returns the expected role' do + expect(subject['roles'].first).to include({ 'name' => 'Role' }) + end + end + + context 'when the account has a non-highlighted role' do + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: false) } + + it 'returns empty roles' do + expect(subject['roles']).to eq [] + end + end +end diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index d21270c79..28f20e9c7 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -138,7 +138,26 @@ RSpec.describe PostStatusService, type: :service do status = subject.call(account, text: "test status update") expect(ProcessMentionsService).to have_received(:new) - expect(mention_service).to have_received(:call).with(status) + expect(mention_service).to have_received(:call).with(status, save_records: false) + end + + it 'safeguards mentions' do + account = Fabricate(:account) + mentioned_account = Fabricate(:account, username: 'alice') + unexpected_mentioned_account = Fabricate(:account, username: 'bob') + + expect do + subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id]) + end.to raise_error(an_instance_of(PostStatusService::UnexpectedMentionsError).and having_attributes(accounts: [unexpected_mentioned_account])) + end + + it 'processes duplicate mentions correctly' do + account = Fabricate(:account) + mentioned_account = Fabricate(:account, username: 'alice') + + expect do + subject.call(account, text: '@alice @alice @alice hey @alice') + end.not_to raise_error end it 'processes hashtags' do diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index 5b9d17a4c..0dd62c807 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -47,6 +47,19 @@ RSpec.describe ProcessMentionsService, type: :service do end end + context 'mentioning a user several times when not saving records' do + let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct} @#{remote_user.acct} @#{remote_user.acct}", visibility: :public) } + + before do + subject.call(status, save_records: false) + end + + it 'creates exactly one mention' do + expect(status.mentions.size).to eq 1 + end + end + context 'with an IDN domain' do let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } diff --git a/streaming/index.js b/streaming/index.js index dbebb573e..8ee19ae70 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -864,15 +864,15 @@ const startWorker = async (workerId) => { res.write('# TYPE connected_channels gauge\n'); res.write('# HELP connected_channels The number of Redis channels the streaming server is subscribed to\n'); res.write(`connected_channels ${Object.keys(subs).length}.0\n`); - res.write('# TYPE pg.pool.total_connections gauge \n'); - res.write('# HELP pg.pool.total_connections The total number of clients existing within the pool\n'); - res.write(`pg.pool.total_connections ${pgPool.totalCount}.0\n`); - res.write('# TYPE pg.pool.idle_connections gauge \n'); - res.write('# HELP pg.pool.idle_connections The number of clients which are not checked out but are currently idle in the pool\n'); - res.write(`pg.pool.idle_connections ${pgPool.idleCount}.0\n`); - res.write('# TYPE pg.pool.waiting_queries gauge \n'); - res.write('# HELP pg.pool.waiting_queries The number of queued requests waiting on a client when all clients are checked out\n'); - res.write(`pg.pool.waiting_queries ${pgPool.waitingCount}.0\n`); + res.write('# TYPE pg_pool_total_connections gauge\n'); + res.write('# HELP pg_pool_total_connections The total number of clients existing within the pool\n'); + res.write(`pg_pool_total_connections ${pgPool.totalCount}.0\n`); + res.write('# TYPE pg_pool_idle_connections gauge\n'); + res.write('# HELP pg_pool_idle_connections The number of clients which are not checked out but are currently idle in the pool\n'); + res.write(`pg_pool_idle_connections ${pgPool.idleCount}.0\n`); + res.write('# TYPE pg_pool_waiting_queries gauge\n'); + res.write('# HELP pg_pool_waiting_queries The number of queued requests waiting on a client when all clients are checked out\n'); + res.write(`pg_pool_waiting_queries ${pgPool.waitingCount}.0\n`); res.write('# EOF\n'); res.end(); })); diff --git a/stylelint.config.js b/stylelint.config.js index 0f8267a81..c8c07a05b 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -10,7 +10,6 @@ module.exports = { 'color-function-notation': null, 'color-hex-length': null, 'declaration-block-no-redundant-longhand-properties': null, - 'max-line-length': null, 'no-descending-specificity': null, 'no-duplicate-selectors': null, 'number-max-precision': 8, @@ -18,7 +17,6 @@ module.exports = { 'property-no-vendor-prefix': null, 'selector-class-pattern': null, 'selector-id-pattern': null, - 'string-quotes': null, 'value-keyword-case': null, 'value-no-vendor-prefix': null, diff --git a/yarn.lock b/yarn.lock index e0dcd6eab..033e43ebe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1076,10 +1076,25 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@csstools/selector-specificity@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" - integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== +"@csstools/css-parser-algorithms@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz#ff02629c7c95d1f4f8ea84d5ef1173461610535e" + integrity sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw== + +"@csstools/css-tokenizer@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.0.1.tgz#cb1e11752db57e69d9aa0e84c3105a25845d4055" + integrity sha512-sYD3H7ReR88S/4+V5VbKiBEUJF4FqvG+8aNJkxqoPAnbhFziDG22IDZc4+h+xA63SfgM+h15lq5OnLeCxQ9nPA== + +"@csstools/media-query-list-parser@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz#d85a366811563a5d002755ed10e5212a1613c91d" + integrity sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA== + +"@csstools/selector-specificity@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz#c9c61d9fe5ca5ac664e1153bb0aa0eba1c6d6308" + integrity sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw== "@emotion/babel-plugin@^11.7.1": version "11.9.2" @@ -3567,7 +3582,7 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cosmiconfig@^7.0.0, cosmiconfig@^7.1.0: +cosmiconfig@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== @@ -3578,6 +3593,16 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.1.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.0.0.tgz#e9feae014eab580f858f8a0288f38997a7bebe97" + integrity sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -3723,6 +3748,14 @@ css-tree@1.0.0-alpha.39: mdn-data "2.0.6" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + css-what@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" @@ -5744,7 +5777,7 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore@^5.2.0, ignore@^5.2.1: +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -7326,6 +7359,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -8716,7 +8754,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27, postcss@^7.0.32: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.2.15, postcss@^8.4.19, postcss@^8.4.21: +postcss@^8.2.15, postcss@^8.4.21: version "8.4.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== @@ -10039,7 +10077,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -10436,39 +10474,39 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -stylelint-config-recommended-scss@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz#1c1e93e619fe2275d4c1067928d92e0614f7d64f" - integrity sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ== +stylelint-config-recommended-scss@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-9.0.0.tgz#e755cf3654f3a3a6d7bdf84fe0a814595754a386" + integrity sha512-5e9pn3Ztfncd8s9OqvvCW7tZpYe+vGmPi7VEXX7XEp+Kj38PnKCrvFCBL+hQ7rkD4d5QzjB3BxlFEyo/30UWUw== dependencies: postcss-scss "^4.0.2" - stylelint-config-recommended "^9.0.0" - stylelint-scss "^4.0.0" + stylelint-config-recommended "^10.0.1" + stylelint-scss "^4.4.0" -stylelint-config-recommended@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz#1c9e07536a8cd875405f8ecef7314916d94e7e40" - integrity sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ== +stylelint-config-recommended@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-10.0.1.tgz#25a8828acf6cde87dac6db2950c8c4ed82a69ae1" + integrity sha512-TQ4xQ48tW4QSlODcti7pgSRqBZcUaBzuh0jPpfiMhwJKBPkqzTIAU+IrSWL/7BgXlOM90DjB7YaNgFpx8QWhuA== -stylelint-config-standard-scss@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-6.1.0.tgz#a6cddd2a9430578b92fc89726a59474d5548a444" - integrity sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q== +stylelint-config-standard-scss@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-7.0.0.tgz#c7076bf5afa705d9e5ddc3ede39da7a684fa0f60" + integrity sha512-rHgydRJxN4Q9lDcwrLFoiFA3S8CRqsUcyBBCLwEMjIwzJViluFfsOKFPSomx6hScVQgQ4//Fx0hRKiSHyO0ihw== dependencies: - stylelint-config-recommended-scss "^8.0.0" - stylelint-config-standard "^29.0.0" + stylelint-config-recommended-scss "^9.0.0" + stylelint-config-standard "^30.0.1" -stylelint-config-standard@^29.0.0: - version "29.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz#4cc0e0f05512a39bb8b8e97853247d3a95d66fa2" - integrity sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg== +stylelint-config-standard@^30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-30.0.1.tgz#a84d57c240c37f7db47023ab9d2e64c49090e1eb" + integrity sha512-NbeHOmpRQhjZh5XB1B/S4MLRWvz4xxAxeDBjzl0tY2xEcayNhLbaRGF0ZQzq+DQZLCcPpOHeS2Ru1ydbkhkmLg== dependencies: - stylelint-config-recommended "^9.0.0" + stylelint-config-recommended "^10.0.1" -stylelint-scss@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.2.0.tgz#e25fd390ee38a7e89fcfaec2a8f9dce2ec6ddee8" - integrity sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA== +stylelint-scss@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.4.0.tgz#87ce9d049eff1ce67cce788780fbfda63099017e" + integrity sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw== dependencies: lodash "^4.17.21" postcss-media-query-parser "^0.2.3" @@ -10476,16 +10514,20 @@ stylelint-scss@^4.0.0: postcss-selector-parser "^6.0.6" postcss-value-parser "^4.1.0" -stylelint@^14.16.1: - version "14.16.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.16.1.tgz#b911063530619a1bbe44c2b875fd8181ebdc742d" - integrity sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A== +stylelint@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.1.0.tgz#24d7cbe06250ceca3b276393bfdeaaaba4356195" + integrity sha512-Tw8OyIiYhxnIHUzgoLlCyWgCUKsPYiP3TDgs7M1VbayS+q5qZly2yxABg+YPe/hFRWiu0cOtptCtpyrn1CrnYw== dependencies: - "@csstools/selector-specificity" "^2.0.2" + "@csstools/css-parser-algorithms" "^2.0.1" + "@csstools/css-tokenizer" "^2.0.1" + "@csstools/media-query-list-parser" "^2.0.1" + "@csstools/selector-specificity" "^2.1.1" balanced-match "^2.0.0" colord "^2.9.3" - cosmiconfig "^7.1.0" + cosmiconfig "^8.0.0" css-functions-list "^3.1.0" + css-tree "^2.3.1" debug "^4.3.4" fast-glob "^3.2.12" fastest-levenshtein "^1.0.16" @@ -10494,7 +10536,7 @@ stylelint@^14.16.1: globby "^11.1.0" globjoin "^0.1.4" html-tags "^3.2.0" - ignore "^5.2.1" + ignore "^5.2.4" import-lazy "^4.0.0" imurmurhash "^0.1.4" is-plain-object "^5.0.0" @@ -10504,7 +10546,7 @@ stylelint@^14.16.1: micromatch "^4.0.5" normalize-path "^3.0.0" picocolors "^1.0.0" - postcss "^8.4.19" + postcss "^8.4.21" postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" postcss-safe-parser "^6.0.0" @@ -10518,7 +10560,7 @@ stylelint@^14.16.1: svg-tags "^1.0.0" table "^6.8.1" v8-compile-cache "^2.3.0" - write-file-atomic "^4.0.2" + write-file-atomic "^5.0.0" stylis@4.0.13: version "4.0.13" @@ -11788,14 +11830,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - write-file-atomic@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.0.tgz#54303f117e109bf3d540261125c8ea5a7320fab0"