From bb51c0676d0cf27babc2c01ee337ca5fd24ae37c Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Fri, 18 Aug 2023 12:06:08 +0200 Subject: [PATCH 01/52] Remove redundant ready() wrapper (#26533) --- app/javascript/packs/public.jsx | 471 ++++++++++++++++---------------- 1 file changed, 234 insertions(+), 237 deletions(-) diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index da43bba7d..9e30ecaa0 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -65,287 +65,284 @@ function loaded() { }; }; - ready(() => { - const locale = document.documentElement.lang; - - const dateTimeFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - - const dateFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'short', - day: 'numeric', - timeFormat: false, - }); - - const timeFormat = new Intl.DateTimeFormat(locale, { - timeStyle: 'short', - hour12: false, - }); - - const formatMessage = ({ id, defaultMessage }, values) => { - const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); - return messageFormat.format(values); - }; + const locale = document.documentElement.lang; + + const dateTimeFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }); - [].forEach.call(document.querySelectorAll('.emojify'), (content) => { - content.innerHTML = emojify(content.innerHTML); - }); + const dateFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + timeFormat: false, + }); - [].forEach.call(document.querySelectorAll('time.formatted'), (content) => { - const datetime = new Date(content.getAttribute('datetime')); - const formattedDate = dateTimeFormat.format(datetime); + const timeFormat = new Intl.DateTimeFormat(locale, { + timeStyle: 'short', + hour12: false, + }); - content.title = formattedDate; - content.textContent = formattedDate; - }); + const formatMessage = ({ id, defaultMessage }, values) => { + const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); + return messageFormat.format(values); + }; - const isToday = date => { - const today = new Date(); + [].forEach.call(document.querySelectorAll('.emojify'), (content) => { + content.innerHTML = emojify(content.innerHTML); + }); - return date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear(); - }; - const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); - - [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => { - const datetime = new Date(content.getAttribute('datetime')); - - let formattedContent; - - if (isToday(datetime)) { - const formattedTime = timeFormat.format(datetime); - - formattedContent = todayFormat.format({ time: formattedTime }); - } else { - formattedContent = dateFormat.format(datetime); - } - - content.title = formattedContent; - content.textContent = formattedContent; - }); - - [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { - const datetime = new Date(content.getAttribute('datetime')); - const now = new Date(); - - const timeGiven = content.getAttribute('datetime').includes('T'); - content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); - content.textContent = timeAgoString({ - formatMessage, - formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), - }, datetime, now, now.getFullYear(), timeGiven); - }); - - const reactComponents = document.querySelectorAll('[data-component]'); - - if (reactComponents.length > 0) { - import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container') - .then(({ default: MediaContainer }) => { - [].forEach.call(reactComponents, (component) => { - [].forEach.call(component.children, (child) => { - component.removeChild(child); - }); - }); + [].forEach.call(document.querySelectorAll('time.formatted'), (content) => { + const datetime = new Date(content.getAttribute('datetime')); + const formattedDate = dateTimeFormat.format(datetime); - const content = document.createElement('div'); + content.title = formattedDate; + content.textContent = formattedDate; + }); - const root = createRoot(content); - root.render(); - document.body.appendChild(content); - scrollToDetailedStatus(); - }) - .catch(error => { - console.error(error); - scrollToDetailedStatus(); - }); + const isToday = date => { + const today = new Date(); + + return date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear(); + }; + const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); + + [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => { + const datetime = new Date(content.getAttribute('datetime')); + + let formattedContent; + + if (isToday(datetime)) { + const formattedTime = timeFormat.format(datetime); + + formattedContent = todayFormat.format({ time: formattedTime }); } else { - scrollToDetailedStatus(); + formattedContent = dateFormat.format(datetime); } - delegate(document, '#user_account_attributes_username', 'input', throttle(() => { - const username = document.getElementById('user_account_attributes_username'); + content.title = formattedContent; + content.textContent = formattedContent; + }); + + [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { + const datetime = new Date(content.getAttribute('datetime')); + const now = new Date(); - if (username.value && username.value.length > 0) { - axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { - username.setCustomValidity(formatMessage(messages.usernameTaken)); - }).catch(() => { - username.setCustomValidity(''); + const timeGiven = content.getAttribute('datetime').includes('T'); + content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); + content.textContent = timeAgoString({ + formatMessage, + formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), + }, datetime, now, now.getFullYear(), timeGiven); + }); + + const reactComponents = document.querySelectorAll('[data-component]'); + + if (reactComponents.length > 0) { + import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container') + .then(({ default: MediaContainer }) => { + [].forEach.call(reactComponents, (component) => { + [].forEach.call(component.children, (child) => { + component.removeChild(child); + }); }); - } else { + + const content = document.createElement('div'); + + const root = createRoot(content); + root.render(); + document.body.appendChild(content); + scrollToDetailedStatus(); + }) + .catch(error => { + console.error(error); + scrollToDetailedStatus(); + }); + } else { + scrollToDetailedStatus(); + } + + delegate(document, '#user_account_attributes_username', 'input', throttle(() => { + const username = document.getElementById('user_account_attributes_username'); + + if (username.value && username.value.length > 0) { + axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { + username.setCustomValidity(formatMessage(messages.usernameTaken)); + }).catch(() => { username.setCustomValidity(''); - } - }, 500, { leading: false, trailing: true })); - - delegate(document, '#user_password,#user_password_confirmation', 'input', () => { - const password = document.getElementById('user_password'); - const confirmation = document.getElementById('user_password_confirmation'); - if (!confirmation) return; - - if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); - } else { - confirmation.setCustomValidity(''); - } - }); - - delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); - delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); - - delegate(document, '.status__content__spoiler-link', 'click', function() { - const statusEl = this.parentNode.parentNode; - - if (statusEl.dataset.spoiler === 'expanded') { - statusEl.dataset.spoiler = 'folded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); - } else { - statusEl.dataset.spoiler = 'expanded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); - } - - return false; - }); - - [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { - const statusEl = spoilerLink.parentNode.parentNode; - const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); - spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); - }); - }); + }); + } else { + username.setCustomValidity(''); + } + }, 500, { leading: false, trailing: true })); + + delegate(document, '#user_password,#user_password_confirmation', 'input', () => { + const password = document.getElementById('user_password'); + const confirmation = document.getElementById('user_password_confirmation'); + if (!confirmation) return; - delegate(document, '#account_display_name', 'input', ({ target }) => { - const name = document.querySelector('.card .display-name strong'); - if (name) { - if (target.value) { - name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); - } else { - name.textContent = target.dataset.default; - } + if (confirmation.value && confirmation.value.length > password.maxLength) { + confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); + } else if (password.value && password.value !== confirmation.value) { + confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); + } else { + confirmation.setCustomValidity(''); } }); - delegate(document, '#account_avatar', 'change', ({ target }) => { - const avatar = document.querySelector('.card .avatar img'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); + delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); - avatar.src = url; + delegate(document, '.status__content__spoiler-link', 'click', function() { + const statusEl = this.parentNode.parentNode; + + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; + this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); + } else { + statusEl.dataset.spoiler = 'expanded'; + this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); + } + + return false; }); - const getProfileAvatarAnimationHandler = (swapTo) => { - //animate avatar gifs on the profile page when moused over - return ({ target }) => { - const swapSrc = target.getAttribute(swapTo); - //only change the img source if autoplay is off and the image src is actually different - if(target.getAttribute('data-autoplay') !== 'true' && target.src !== swapSrc) { - target.src = swapSrc; - } - }; + [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { + const statusEl = spoilerLink.parentNode.parentNode; + const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); + spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); + }); +} + +delegate(document, '#account_display_name', 'input', ({ target }) => { + const name = document.querySelector('.card .display-name strong'); + if (name) { + if (target.value) { + name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); + } else { + name.textContent = target.dataset.default; + } + } +}); + +delegate(document, '#account_avatar', 'change', ({ target }) => { + const avatar = document.querySelector('.card .avatar img'); + const [file] = target.files || []; + const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + + avatar.src = url; +}); + +const getProfileAvatarAnimationHandler = (swapTo) => { + //animate avatar gifs on the profile page when moused over + return ({ target }) => { + const swapSrc = target.getAttribute(swapTo); + //only change the img source if autoplay is off and the image src is actually different + if(target.getAttribute('data-autoplay') !== 'true' && target.src !== swapSrc) { + target.src = swapSrc; + } }; +}; - delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original')); +delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original')); - delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static')); +delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static')); - delegate(document, '#account_header', 'change', ({ target }) => { - const header = document.querySelector('.card .card__img img'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; +delegate(document, '#account_header', 'change', ({ target }) => { + const header = document.querySelector('.card .card__img img'); + const [file] = target.files || []; + const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; - header.src = url; - }); + header.src = url; +}); - delegate(document, '#account_locked', 'change', ({ target }) => { - const lock = document.querySelector('.card .display-name i'); +delegate(document, '#account_locked', 'change', ({ target }) => { + const lock = document.querySelector('.card .display-name i'); - if (lock) { - if (target.checked) { - delete lock.dataset.hidden; - } else { - lock.dataset.hidden = 'true'; - } + if (lock) { + if (target.checked) { + delete lock.dataset.hidden; + } else { + lock.dataset.hidden = 'true'; } - }); + } +}); - delegate(document, '.input-copy input', 'click', ({ target }) => { - target.focus(); - target.select(); - target.setSelectionRange(0, target.value.length); - }); +delegate(document, '.input-copy input', 'click', ({ target }) => { + target.focus(); + target.select(); + target.setSelectionRange(0, target.value.length); +}); - delegate(document, '.input-copy button', 'click', ({ target }) => { - const input = target.parentNode.querySelector('.input-copy__wrapper input'); +delegate(document, '.input-copy button', 'click', ({ target }) => { + const input = target.parentNode.querySelector('.input-copy__wrapper input'); - const oldReadOnly = input.readonly; + const oldReadOnly = input.readonly; - input.readonly = false; - input.focus(); - input.select(); - input.setSelectionRange(0, input.value.length); + input.readonly = false; + input.focus(); + input.select(); + input.setSelectionRange(0, input.value.length); - try { - if (document.execCommand('copy')) { - input.blur(); - target.parentNode.classList.add('copied'); + try { + if (document.execCommand('copy')) { + input.blur(); + target.parentNode.classList.add('copied'); - setTimeout(() => { - target.parentNode.classList.remove('copied'); - }, 700); - } - } catch (err) { - console.error(err); + setTimeout(() => { + target.parentNode.classList.remove('copied'); + }, 700); } + } catch (err) { + console.error(err); + } - input.readonly = oldReadOnly; - }); + input.readonly = oldReadOnly; +}); - const toggleSidebar = () => { - const sidebar = document.querySelector('.sidebar ul'); - const toggleButton = document.querySelector('.sidebar__toggle__icon'); +const toggleSidebar = () => { + const sidebar = document.querySelector('.sidebar ul'); + const toggleButton = document.querySelector('.sidebar__toggle__icon'); - if (sidebar.classList.contains('visible')) { - document.body.style.overflow = null; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - document.body.style.overflow = 'hidden'; - toggleButton.setAttribute('aria-expanded', 'true'); - } + if (sidebar.classList.contains('visible')) { + document.body.style.overflow = null; + toggleButton.setAttribute('aria-expanded', 'false'); + } else { + document.body.style.overflow = 'hidden'; + toggleButton.setAttribute('aria-expanded', 'true'); + } - toggleButton.classList.toggle('active'); - sidebar.classList.toggle('visible'); - }; + toggleButton.classList.toggle('active'); + sidebar.classList.toggle('visible'); +}; + +delegate(document, '.sidebar__toggle__icon', 'click', () => { + toggleSidebar(); +}); - delegate(document, '.sidebar__toggle__icon', 'click', () => { +delegate(document, '.sidebar__toggle__icon', 'keydown', e => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); toggleSidebar(); - }); + } +}); - delegate(document, '.sidebar__toggle__icon', 'keydown', e => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - toggleSidebar(); +// Empty the honeypot fields in JS in case something like an extension +// automatically filled them. +delegate(document, '#registration_new_user,#new_user', 'submit', () => { + ['user_website', 'user_confirm_password', 'registration_user_website', 'registration_user_confirm_password'].forEach(id => { + const field = document.getElementById(id); + if (field) { + field.value = ''; } }); - - // Empty the honeypot fields in JS in case something like an extension - // automatically filled them. - delegate(document, '#registration_new_user,#new_user', 'submit', () => { - ['user_website', 'user_confirm_password', 'registration_user_website', 'registration_user_confirm_password'].forEach(id => { - const field = document.getElementById(id); - if (field) { - field.value = ''; - } - }); - }); -} - +}); function main() { ready(loaded); From 6375e390af924649348832d30568e0948871e92b Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Fri, 18 Aug 2023 15:05:35 +0200 Subject: [PATCH 02/52] Fix: support both DATABASE_URL and DB_PASS (#26295) --- streaming/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/streaming/index.js b/streaming/index.js index 2112ca433..a241fa328 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -110,6 +110,11 @@ const pgConfigFromEnv = (env) => { if (env.DATABASE_URL) { baseConfig = dbUrlToConfig(env.DATABASE_URL); + + // Support overriding the database password in the connection URL + if (!baseConfig.password && env.DB_PASS) { + baseConfig.password = env.DB_PASS; + } } else { baseConfig = pgConfigs[environment]; From e7bea8f004711b34f7abe7b6517adfabe0e5626f Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 18 Aug 2023 16:06:46 +0200 Subject: [PATCH 03/52] Fix already initialized constant warning (#26542) --- lib/http_extensions.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/http_extensions.rb b/lib/http_extensions.rb index 2bc0618c4..048f85f87 100644 --- a/lib/http_extensions.rb +++ b/lib/http_extensions.rb @@ -2,9 +2,7 @@ # Monkey patching until https://github.com/httprb/http/pull/757 is merged unless HTTP::Request::METHODS.include?(:purge) - module HTTP - class Request - METHODS = METHODS.dup.push(:purge).freeze - end - end + methods = HTTP::Request::METHODS.dup + HTTP::Request.send(:remove_const, :METHODS) + HTTP::Request.const_set(:METHODS, methods.push(:purge).freeze) end From ee702e36e58d638bcf75b2eae2ca86499693465e Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 18 Aug 2023 18:20:55 +0200 Subject: [PATCH 04/52] Change follow recommendation materialized view to be faster in most cases (#26545) Co-authored-by: Renaud Chaput --- app/models/follow_recommendation.rb | 3 +- ...56_create_global_follow_recommendations.rb | 8 +++++ ...30818142253_drop_follow_recommendations.rb | 12 +++++++ db/schema.rb | 20 ++++++------ .../global_follow_recommendations_v01.sql | 32 +++++++++++++++++++ 5 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20230818141056_create_global_follow_recommendations.rb create mode 100644 db/post_migrate/20230818142253_drop_follow_recommendations.rb create mode 100644 db/views/global_follow_recommendations_v01.sql diff --git a/app/models/follow_recommendation.rb b/app/models/follow_recommendation.rb index 123570b12..9d2648394 100644 --- a/app/models/follow_recommendation.rb +++ b/app/models/follow_recommendation.rb @@ -2,7 +2,7 @@ # == Schema Information # -# Table name: follow_recommendations +# Table name: global_follow_recommendations # # account_id :bigint(8) primary key # rank :decimal(, ) @@ -11,6 +11,7 @@ class FollowRecommendation < ApplicationRecord self.primary_key = :account_id + self.table_name = :global_follow_recommendations belongs_to :account_summary, foreign_key: :account_id, inverse_of: false belongs_to :account diff --git a/db/migrate/20230818141056_create_global_follow_recommendations.rb b/db/migrate/20230818141056_create_global_follow_recommendations.rb new file mode 100644 index 000000000..b88c71b9d --- /dev/null +++ b/db/migrate/20230818141056_create_global_follow_recommendations.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class CreateGlobalFollowRecommendations < ActiveRecord::Migration[7.0] + def change + create_view :global_follow_recommendations, materialized: { no_data: true } + safety_assured { add_index :global_follow_recommendations, :account_id, unique: true } + end +end diff --git a/db/post_migrate/20230818142253_drop_follow_recommendations.rb b/db/post_migrate/20230818142253_drop_follow_recommendations.rb new file mode 100644 index 000000000..95913d6ca --- /dev/null +++ b/db/post_migrate/20230818142253_drop_follow_recommendations.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class DropFollowRecommendations < ActiveRecord::Migration[7.0] + def up + drop_view :follow_recommendations, materialized: true + end + + def down + create_view :follow_recommendations, version: 2, materialized: { no_data: true } + safety_assured { add_index :follow_recommendations, :account_id, unique: true } + end +end diff --git a/db/schema.rb b/db/schema.rb index 7cca196ea..8b758fc7d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_08_14_223300) do +ActiveRecord::Schema[7.0].define(version: 2023_08_18_142253) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1331,34 +1331,36 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_14_223300) do SQL add_index "account_summaries", ["account_id"], name: "index_account_summaries_on_account_id", unique: true - create_view "follow_recommendations", materialized: true, sql_definition: <<-SQL + create_view "global_follow_recommendations", materialized: true, sql_definition: <<-SQL SELECT t0.account_id, sum(t0.rank) AS rank, array_agg(t0.reason) AS reason FROM ( SELECT account_summaries.account_id, ((count(follows.id))::numeric / (1.0 + (count(follows.id))::numeric)) AS rank, 'most_followed'::text AS reason - FROM (((follows + FROM ((follows JOIN account_summaries ON ((account_summaries.account_id = follows.target_account_id))) JOIN users ON ((users.account_id = follows.account_id))) - LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = follows.target_account_id))) - WHERE ((users.current_sign_in_at >= (now() - 'P30D'::interval)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL)) + WHERE ((users.current_sign_in_at >= (now() - 'P30D'::interval)) AND (account_summaries.sensitive = false) AND (NOT (EXISTS ( SELECT 1 + FROM follow_recommendation_suppressions + WHERE (follow_recommendation_suppressions.account_id = follows.target_account_id))))) GROUP BY account_summaries.account_id HAVING (count(follows.id) >= 5) UNION ALL SELECT account_summaries.account_id, (sum((status_stats.reblogs_count + status_stats.favourites_count)) / (1.0 + sum((status_stats.reblogs_count + status_stats.favourites_count)))) AS rank, 'most_interactions'::text AS reason - FROM (((status_stats + FROM ((status_stats JOIN statuses ON ((statuses.id = status_stats.status_id))) JOIN account_summaries ON ((account_summaries.account_id = statuses.account_id))) - LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = statuses.account_id))) - WHERE ((statuses.id >= (((date_part('epoch'::text, (now() - 'P30D'::interval)) * (1000)::double precision))::bigint << 16)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL)) + WHERE ((statuses.id >= (((date_part('epoch'::text, (now() - 'P30D'::interval)) * (1000)::double precision))::bigint << 16)) AND (account_summaries.sensitive = false) AND (NOT (EXISTS ( SELECT 1 + FROM follow_recommendation_suppressions + WHERE (follow_recommendation_suppressions.account_id = statuses.account_id))))) GROUP BY account_summaries.account_id HAVING (sum((status_stats.reblogs_count + status_stats.favourites_count)) >= (5)::numeric)) t0 GROUP BY t0.account_id ORDER BY (sum(t0.rank)) DESC; SQL - add_index "follow_recommendations", ["account_id"], name: "index_follow_recommendations_on_account_id", unique: true + add_index "global_follow_recommendations", ["account_id"], name: "index_global_follow_recommendations_on_account_id", unique: true end diff --git a/db/views/global_follow_recommendations_v01.sql b/db/views/global_follow_recommendations_v01.sql new file mode 100644 index 000000000..de693c9fc --- /dev/null +++ b/db/views/global_follow_recommendations_v01.sql @@ -0,0 +1,32 @@ +SELECT + account_id, + sum(rank) AS rank, + array_agg(reason) AS reason +FROM ( + SELECT + account_summaries.account_id AS account_id, + count(follows.id) / (1.0 + count(follows.id)) AS rank, + 'most_followed' AS reason + FROM follows + INNER JOIN account_summaries ON account_summaries.account_id = follows.target_account_id + INNER JOIN users ON users.account_id = follows.account_id + WHERE users.current_sign_in_at >= (now() - interval '30 days') + AND account_summaries.sensitive = 'f' + AND NOT EXISTS (SELECT 1 FROM follow_recommendation_suppressions WHERE follow_recommendation_suppressions.account_id = follows.target_account_id) + GROUP BY account_summaries.account_id + HAVING count(follows.id) >= 5 + UNION ALL + SELECT account_summaries.account_id AS account_id, + sum(status_stats.reblogs_count + status_stats.favourites_count) / (1.0 + sum(status_stats.reblogs_count + status_stats.favourites_count)) AS rank, + 'most_interactions' AS reason + FROM status_stats + INNER JOIN statuses ON statuses.id = status_stats.status_id + INNER JOIN account_summaries ON account_summaries.account_id = statuses.account_id + WHERE statuses.id >= ((date_part('epoch', now() - interval '30 days') * 1000)::bigint << 16) + AND account_summaries.sensitive = 'f' + AND NOT EXISTS (SELECT 1 FROM follow_recommendation_suppressions WHERE follow_recommendation_suppressions.account_id = statuses.account_id) + GROUP BY account_summaries.account_id + HAVING sum(status_stats.reblogs_count + status_stats.favourites_count) >= 5 +) t0 +GROUP BY account_id +ORDER BY rank DESC From bb23116e8d17b0fa7564a0f713c48753cfba7023 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Fri, 18 Aug 2023 18:24:32 +0200 Subject: [PATCH 05/52] Fix profile picture preview (#26538) --- app/javascript/packs/public.jsx | 12 ++---------- app/javascript/styles/mastodon/forms.scss | 10 ++++++++++ app/views/settings/profiles/show.html.haml | 16 ++++++++-------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index 9e30ecaa0..1d917d60e 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -231,8 +231,8 @@ delegate(document, '#account_display_name', 'input', ({ target }) => { } }); -delegate(document, '#account_avatar', 'change', ({ target }) => { - const avatar = document.querySelector('.card .avatar img'); +delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => { + const avatar = document.getElementById(target.id + '-preview'); const [file] = target.files || []; const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; @@ -254,14 +254,6 @@ delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnima delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static')); -delegate(document, '#account_header', 'change', ({ target }) => { - const header = document.querySelector('.card .card__img img'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; - - header.src = url; -}); - delegate(document, '#account_locked', 'change', ({ target }) => { const lock = document.querySelector('.card .display-name i'); diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index f69b699a0..beb45ab6e 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -309,9 +309,19 @@ code { border-radius: 4px; background: url('images/void.png'); + &[src$='missing.png'] { + visibility: hidden; + } + &:last-child { margin-bottom: 0; } + + &#account_avatar-preview { + width: 90px; + height: 90px; + object-fit: cover; + } } } diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 92b7f4256..7c13dc7f4 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -35,10 +35,10 @@ .fields-group = f.input :avatar, wrapper: :with_block_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT)) - - if @account.avatar.present? - .fields-row__column.fields-row__column-6 - .fields-group - = image_tag @account.avatar.url, class: 'fields-group__thumbnail', width: 90, height: 90 + .fields-row__column.fields-row__column-6 + .fields-group + = image_tag @account.avatar.url, class: 'fields-group__thumbnail', id: 'account_avatar-preview' + - if @account.avatar.present? = link_to settings_profile_picture_path('avatar'), data: { method: :delete }, class: 'link-button link-button--destructive' do = fa_icon 'trash fw' = t('generic.delete') @@ -48,10 +48,10 @@ .fields-group = f.input :header, wrapper: :with_block_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(AccountHeader::LIMIT)) - - if @account.header.present? - .fields-row__column.fields-row__column-6 - .fields-group - = image_tag @account.header.url, class: 'fields-group__thumbnail' + .fields-row__column.fields-row__column-6 + .fields-group + = image_tag @account.header.url, class: 'fields-group__thumbnail', id: 'account_header-preview' + - if @account.header.present? = link_to settings_profile_picture_path('header'), data: { method: :delete }, class: 'link-button link-button--destructive' do = fa_icon 'trash fw' = t('generic.delete') From d24a87ce4fd9bb91733f25a3f3d975bc39010d81 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 21 Aug 2023 03:44:35 -0300 Subject: [PATCH 06/52] Add ability to delete avatar or header picture via the API (#25124) Co-authored-by: Claire --- app/controllers/api/v1/profiles_controller.rb | 29 +++++ config/routes/api.rb | 1 + spec/requests/api/v1/profiles_spec.rb | 112 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 app/controllers/api/v1/profiles_controller.rb create mode 100644 spec/requests/api/v1/profiles_spec.rb diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb new file mode 100644 index 000000000..f781f0089 --- /dev/null +++ b/app/controllers/api/v1/profiles_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Api::V1::ProfilesController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + before_action :set_image + before_action :validate_image_param + + def destroy + @account = current_account + UpdateAccountService.new.call(@account, { @image => nil }, raise_error: true) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + render json: @account, serializer: REST::CredentialAccountSerializer + end + + private + + def set_image + @image = params[:image] + end + + def validate_image_param + raise(Mastodon::InvalidParameterError, 'Image must be either "avatar" or "header"') unless valid_image? + end + + def valid_image? + %w(avatar header).include?(@image) + end +end diff --git a/config/routes/api.rb b/config/routes/api.rb index 48c4c6522..dc6aea7f7 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -95,6 +95,7 @@ namespace :api, format: false do resources :filters, only: [:index, :create, :show, :update, :destroy] resources :endorsements, only: [:index] resources :markers, only: [:index, :create] + resources :profile, only: :destroy, param: :image, controller: 'profiles' namespace :apps do get :verify_credentials, to: 'credentials#show' diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb new file mode 100644 index 000000000..9fa5fd329 --- /dev/null +++ b/spec/requests/api/v1/profiles_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Deleting profile images' do + let(:account) do + Fabricate( + :account, + avatar: fixture_file_upload('avatar.gif', 'image/gif'), + header: fixture_file_upload('attachment.jpg', 'image/jpeg') + ) + end + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: account.user.id, scopes: scopes) } + let(:scopes) { 'write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'DELETE /api/v1/profile' do + before do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) + end + + context 'when deleting an avatar' do + context 'with wrong scope' do + before do + delete '/api/v1/profile/avatar', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read' + end + + it 'returns http success' do + delete '/api/v1/profile/avatar', headers: headers + + expect(response).to have_http_status(200) + end + + it 'deletes the avatar' do + delete '/api/v1/profile/avatar', headers: headers + + account.reload + + expect(account.avatar).to_not exist + end + + it 'does not delete the header' do + delete '/api/v1/profile/avatar', headers: headers + + account.reload + + expect(account.header).to exist + end + + it 'queues up an account update distribution' do + delete '/api/v1/profile/avatar', headers: headers + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) + end + end + + context 'when deleting a header' do + context 'with wrong scope' do + before do + delete '/api/v1/profile/header', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read' + end + + it 'returns http success' do + delete '/api/v1/profile/header', headers: headers + + expect(response).to have_http_status(200) + end + + it 'does not delete the avatar' do + delete '/api/v1/profile/header', headers: headers + + account.reload + + expect(account.avatar).to exist + end + + it 'deletes the header' do + delete '/api/v1/profile/header', headers: headers + + account.reload + + expect(account.header).to_not exist + end + + it 'queues up an account update distribution' do + delete '/api/v1/profile/header', headers: headers + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) + end + end + + context 'when provided picture value is invalid' do + it 'returns http bad request' do + delete '/api/v1/profile/invalid', headers: headers + + expect(response).to have_http_status(400) + end + + it 'does not queue up an account update distribution' do + delete '/api/v1/profile/invalid', headers: headers + + expect(ActivityPub::UpdateDistributionWorker).to_not have_received(:perform_async).with(account.id) + end + end + end +end From 872fe2d62db29e2583b4620870b72a56a6e09ff7 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Mon, 21 Aug 2023 08:51:37 +0200 Subject: [PATCH 07/52] Do not start LibreTranslate and Elasticsearch on GitHub Codespaces (#26382) --- .devcontainer/devcontainer.json | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 17208a84e..ce14169aa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,31 +1,29 @@ -// For more details, see https://aka.ms/devcontainer.json. { "name": "Mastodon", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers/features/sshd:1": {} }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or the host. + "runServices": ["app", "db", "redis"], + "forwardPorts": [3000, 4000], - // Use 'postCreateCommand' to run commands after the container is created. + "containerEnv": { + "ES_ENABLED": "", + "LIBRE_TRANSLATE_ENDPOINT": "" + }, + "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "postCreateCommand": ".devcontainer/post-create.sh", "waitFor": "postCreateCommand", - // Configure tool-specific properties. "customizations": { - // Configure properties specific to VS Code. "vscode": { - // Set *default* container specific settings.json values on container create. "settings": {}, - // Add the IDs of extensions you want installed when the container is created. "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] } } From d3b4422b9437145c4e1bce1dc8246e646460b32a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:06:33 +0200 Subject: [PATCH 08/52] Update dependency core-js to v3.32.1 (#26548) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7b851a088..5a78fcaf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4301,9 +4301,9 @@ core-js@^2.5.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.30.2: - version "3.32.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.0.tgz#7643d353d899747ab1f8b03d2803b0312a0fb3b6" - integrity sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww== + version "3.32.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.1.tgz#a7d8736a3ed9dd05940c3c4ff32c591bb735be77" + integrity sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ== core-util-is@~1.0.0: version "1.0.3" From 1cdcd9dc08c91321f80ffe4822f6a3da15abeb2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:13:21 +0200 Subject: [PATCH 09/52] Update eslint (non-major) (#26567) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 365 +++++++++++++++++++++++++++++------------------------- 1 file changed, 197 insertions(+), 168 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a78fcaf3..65a0eb036 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1253,10 +1253,10 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== -"@eslint/eslintrc@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93" - integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1268,10 +1268,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.46.0": - version "8.46.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" - integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== +"@eslint/js@^8.47.0": + version "8.47.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d" + integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og== "@floating-ui/core@^1.3.1": version "1.3.1" @@ -2570,49 +2570,48 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^6.0.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.3.0.tgz#e751e148aab7ccaf8a7bfd370f7ce9e6bdd1f3f4" - integrity sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A== + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz#53428b616f7d80fe879f45a08f11cc0f0b62cf13" + integrity sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.3.0" - "@typescript-eslint/type-utils" "6.3.0" - "@typescript-eslint/utils" "6.3.0" - "@typescript-eslint/visitor-keys" "6.3.0" + "@typescript-eslint/scope-manager" "6.4.0" + "@typescript-eslint/type-utils" "6.4.0" + "@typescript-eslint/utils" "6.4.0" + "@typescript-eslint/visitor-keys" "6.4.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" natural-compare "^1.4.0" - natural-compare-lite "^1.4.0" semver "^7.5.4" ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.0.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.3.0.tgz#359684c443f4f848db3c4f14674f544f169c8f46" - integrity sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg== + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.4.0.tgz#47e7c6e22ff1248e8675d95f488890484de67600" + integrity sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg== dependencies: - "@typescript-eslint/scope-manager" "6.3.0" - "@typescript-eslint/types" "6.3.0" - "@typescript-eslint/typescript-estree" "6.3.0" - "@typescript-eslint/visitor-keys" "6.3.0" + "@typescript-eslint/scope-manager" "6.4.0" + "@typescript-eslint/types" "6.4.0" + "@typescript-eslint/typescript-estree" "6.4.0" + "@typescript-eslint/visitor-keys" "6.4.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz#6b74e338c4b88d5e1dfc1a28c570dd5cf8c86b09" - integrity sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ== +"@typescript-eslint/scope-manager@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz#3048e4262ba3eafa4e2e69b08912d9037ec646ae" + integrity sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig== dependencies: - "@typescript-eslint/types" "6.3.0" - "@typescript-eslint/visitor-keys" "6.3.0" + "@typescript-eslint/types" "6.4.0" + "@typescript-eslint/visitor-keys" "6.4.0" -"@typescript-eslint/type-utils@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.3.0.tgz#3bf89ccd36621ddec1b7f8246afe467c67adc247" - integrity sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ== +"@typescript-eslint/type-utils@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.4.0.tgz#c8ac92716ed6a9d5443aa3e342910355b0796ba0" + integrity sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg== dependencies: - "@typescript-eslint/typescript-estree" "6.3.0" - "@typescript-eslint/utils" "6.3.0" + "@typescript-eslint/typescript-estree" "6.4.0" + "@typescript-eslint/utils" "6.4.0" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -2621,10 +2620,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== -"@typescript-eslint/types@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.3.0.tgz#84517f1427923e714b8418981e493b6635ab4c9d" - integrity sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg== +"@typescript-eslint/types@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.4.0.tgz#5b109a59a805f0d8d375895e42d9e5f0037f66ee" + integrity sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg== "@typescript-eslint/typescript-estree@5.59.0": version "5.59.0" @@ -2639,30 +2638,30 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz#20e1e10e2f51cdb9e19a2751215cac92c003643c" - integrity sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg== +"@typescript-eslint/typescript-estree@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.0.tgz#3c58d20632db93fec3d6ab902acbedf593d37276" + integrity sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA== dependencies: - "@typescript-eslint/types" "6.3.0" - "@typescript-eslint/visitor-keys" "6.3.0" + "@typescript-eslint/types" "6.4.0" + "@typescript-eslint/visitor-keys" "6.4.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.3.0.tgz#0898c5e374372c2092ca1b979ea7ee9cc020ce84" - integrity sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q== +"@typescript-eslint/utils@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.4.0.tgz#23e996b693603c5924b1fbb733cc73196256baa5" + integrity sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.3.0" - "@typescript-eslint/types" "6.3.0" - "@typescript-eslint/typescript-estree" "6.3.0" + "@typescript-eslint/scope-manager" "6.4.0" + "@typescript-eslint/types" "6.4.0" + "@typescript-eslint/typescript-estree" "6.4.0" semver "^7.5.4" "@typescript-eslint/visitor-keys@5.59.0": @@ -2673,12 +2672,12 @@ "@typescript-eslint/types" "5.59.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz#8d09aa3e389ae0971426124c155ac289afbe450a" - integrity sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw== +"@typescript-eslint/visitor-keys@6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz#96a426cdb1add28274abd7a34aefe27f8b7d51ef" + integrity sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA== dependencies: - "@typescript-eslint/types" "6.3.0" + "@typescript-eslint/types" "6.4.0" eslint-visitor-keys "^3.4.1" "@webassemblyjs/ast@1.9.0": @@ -3270,6 +3269,13 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +asynciterator.prototype@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" + integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== + dependencies: + has-symbols "^1.0.3" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -5155,7 +5161,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.17.2, es-abstract@^1.21.2: +es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2, es-abstract@^1.21.3: version "1.22.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== @@ -5200,46 +5206,6 @@ es-abstract@^1.17.2, es-abstract@^1.21.2: unbox-primitive "^1.0.2" which-typed-array "^1.1.10" -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== - dependencies: - array-buffer-byte-length "^1.0.0" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.3" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" - es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -5260,6 +5226,26 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" +es-iterator-helpers@^1.0.12: + version "1.0.13" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.13.tgz#72101046ffc19baf9996adc70e6177a26e6e8084" + integrity sha512-LK3VGwzvaPWobO8xzXXGRUOGw8Dcjyfk62CsY/wfHN75CwsJPbuypOYJxK6g5RyEL8YDjIWcl6jgd8foO6mmrA== + dependencies: + asynciterator.prototype "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.21.3" + es-set-tostringtag "^2.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + iterator.prototype "^1.1.0" + safe-array-concat "^1.0.0" + es-set-tostringtag@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" @@ -5327,13 +5313,13 @@ eslint-config-prettier@^9.0.0: integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== eslint-import-resolver-node@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" + is-core-module "^2.13.0" + resolve "^1.22.4" eslint-import-resolver-typescript@^3.5.5: version "3.6.0" @@ -5373,9 +5359,9 @@ eslint-plugin-formatjs@^4.10.1: unicode-emoji-utils "^1.1.1" eslint-plugin-import@~2.28.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz#8d66d6925117b06c4018d491ae84469eb3cb1005" - integrity sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q== + version "2.28.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" + integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== dependencies: array-includes "^3.1.6" array.prototype.findlastindex "^1.2.2" @@ -5386,20 +5372,19 @@ eslint-plugin-import@~2.28.0: eslint-import-resolver-node "^0.3.7" eslint-module-utils "^2.8.0" has "^1.0.3" - is-core-module "^2.12.1" + is-core-module "^2.13.0" is-glob "^4.0.3" minimatch "^3.1.2" object.fromentries "^2.0.6" object.groupby "^1.0.0" object.values "^1.1.6" - resolve "^1.22.3" semver "^6.3.1" tsconfig-paths "^3.14.2" eslint-plugin-jsdoc@^46.1.0: - version "46.4.6" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.4.6.tgz#5226461eda61b5920297cbe02c3b17bc9423cf0b" - integrity sha512-z4SWYnJfOqftZI+b3RM9AtWL1vF/sLWE/LlO9yOKDof9yN2+n3zOdOJTGX/pRE/xnPsooOLG2Rq6e4d+XW3lNw== + version "46.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.5.0.tgz#02e7945701a01fab76e7ced850d4d1eea63c23c0" + integrity sha512-aulXdA4I1dyWpzyS1Nh/GNoS6PavzeucxEapnMR4JUERowWvaEk2Y4A5irpHAcdXtBBHLVe8WIhdXNjoAlGQgA== dependencies: "@es-joy/jsdoccomment" "~0.40.1" are-docs-informative "^0.0.2" @@ -5452,14 +5437,15 @@ eslint-plugin-react-hooks@^4.6.0: integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== eslint-plugin-react@~7.33.0: - version "7.33.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.1.tgz#bc27cccf860ae45413a4a4150bf0977345c1ceab" - integrity sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA== + version "7.33.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" + integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== dependencies: array-includes "^3.1.6" array.prototype.flatmap "^1.3.1" array.prototype.tosorted "^1.1.1" doctrine "^2.1.0" + es-iterator-helpers "^1.0.12" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" @@ -5488,20 +5474,20 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f" - integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.41.0: - version "8.46.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" - integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== + version "8.47.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806" + integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.1" - "@eslint/js" "^8.46.0" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "^8.47.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5512,7 +5498,7 @@ eslint@^8.41.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.2" + eslint-visitor-keys "^3.4.3" espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" @@ -6257,9 +6243,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.21.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" @@ -6843,6 +6829,13 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -6884,20 +6877,13 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0, is-core-module@^2.12.1, is-core-module@^2.13.0, is-core-module@^2.5.0: +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.9.0: version "2.13.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" -is-core-module@^2.9.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -6969,6 +6955,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -6989,6 +6982,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -7150,15 +7150,11 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: has-symbols "^1.0.2" is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.11" is-url@^1.2.4: version "1.2.4" @@ -7276,6 +7272,17 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterator.prototype@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.0.tgz#690c88b043d821f783843aaf725d7ac3b62e3b46" + integrity sha512-rjuhAk1AJ1fssphHD0IFV6TWL40CwRZ53FrztKx43yk2v6rguBYsY4Bj1VU4HmoMmKwZUlx7mfnhDf9cOp4YTw== + dependencies: + define-properties "^1.1.4" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + has-tostringtag "^1.0.0" + reflect.getprototypeof "^1.0.3" + jackspeak@^2.0.3: version "2.2.1" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6" @@ -7856,7 +7863,17 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +jsx-ast-utils@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== @@ -8547,11 +8564,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -10213,6 +10225,18 @@ redux@^4.0.0, redux@^4.2.1: dependencies: "@babel/runtime" "^7.9.2" +reflect.getprototypeof@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.3.tgz#2738fd896fcc3477ffbd4190b40c2458026b6928" + integrity sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.1" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" @@ -10390,7 +10414,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.3: +resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4: version "1.22.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== @@ -10399,7 +10423,7 @@ resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.3: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.19.0, resolve@^1.22.1: +resolve@^1.19.0: version "1.22.2" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== @@ -11257,7 +11281,6 @@ stringz@^2.1.0: char-regex "^1.0.2" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11791,9 +11814,9 @@ trim-newlines@^4.0.2: integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ== ts-api-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" - integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== + version "1.0.2" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.2.tgz#7c094f753b6705ee4faee25c3c684ade52d66d99" + integrity sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ== tsconfig-paths@^3.14.2: version "3.14.2" @@ -12505,6 +12528,24 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + which-collection@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" @@ -12520,7 +12561,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.10: +which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.9: version "1.1.11" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== @@ -12531,18 +12572,6 @@ which-typed-array@^1.1.10: gopd "^1.0.1" has-tostringtag "^1.0.0" -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" - which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From 191d302b7f15a3619ded9c92d790effc5f54c6ce Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 21 Aug 2023 15:47:09 +0200 Subject: [PATCH 10/52] Refactor `Api::V1::ProfilesController` into two separate controllers (#26573) --- .../api/v1/profile/avatars_controller.rb | 13 +++++++++ .../api/v1/profile/headers_controller.rb | 13 +++++++++ app/controllers/api/v1/profiles_controller.rb | 29 ------------------- config/routes/api.rb | 6 +++- spec/requests/api/v1/profiles_spec.rb | 14 --------- 5 files changed, 31 insertions(+), 44 deletions(-) create mode 100644 app/controllers/api/v1/profile/avatars_controller.rb create mode 100644 app/controllers/api/v1/profile/headers_controller.rb delete mode 100644 app/controllers/api/v1/profiles_controller.rb diff --git a/app/controllers/api/v1/profile/avatars_controller.rb b/app/controllers/api/v1/profile/avatars_controller.rb new file mode 100644 index 000000000..bc4d01a59 --- /dev/null +++ b/app/controllers/api/v1/profile/avatars_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Api::V1::Profile::AvatarsController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + + def destroy + @account = current_account + UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + render json: @account, serializer: REST::CredentialAccountSerializer + end +end diff --git a/app/controllers/api/v1/profile/headers_controller.rb b/app/controllers/api/v1/profile/headers_controller.rb new file mode 100644 index 000000000..9f4daa2f7 --- /dev/null +++ b/app/controllers/api/v1/profile/headers_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Api::V1::Profile::HeadersController < Api::BaseController + before_action -> { doorkeeper_authorize! :write, :'write:accounts' } + before_action :require_user! + + def destroy + @account = current_account + UpdateAccountService.new.call(@account, { header: nil }, raise_error: true) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + render json: @account, serializer: REST::CredentialAccountSerializer + end +end diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb deleted file mode 100644 index f781f0089..000000000 --- a/app/controllers/api/v1/profiles_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::ProfilesController < Api::BaseController - before_action -> { doorkeeper_authorize! :write, :'write:accounts' } - before_action :require_user! - before_action :set_image - before_action :validate_image_param - - def destroy - @account = current_account - UpdateAccountService.new.call(@account, { @image => nil }, raise_error: true) - ActivityPub::UpdateDistributionWorker.perform_async(@account.id) - render json: @account, serializer: REST::CredentialAccountSerializer - end - - private - - def set_image - @image = params[:image] - end - - def validate_image_param - raise(Mastodon::InvalidParameterError, 'Image must be either "avatar" or "header"') unless valid_image? - end - - def valid_image? - %w(avatar header).include?(@image) - end -end diff --git a/config/routes/api.rb b/config/routes/api.rb index dc6aea7f7..66eb82f59 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -95,7 +95,11 @@ namespace :api, format: false do resources :filters, only: [:index, :create, :show, :update, :destroy] resources :endorsements, only: [:index] resources :markers, only: [:index, :create] - resources :profile, only: :destroy, param: :image, controller: 'profiles' + + namespace :profile do + resource :avatar, only: :destroy + resource :header, only: :destroy + end namespace :apps do get :verify_credentials, to: 'credentials#show' diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index 9fa5fd329..26a9b848e 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -94,19 +94,5 @@ RSpec.describe 'Deleting profile images' do expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) end end - - context 'when provided picture value is invalid' do - it 'returns http bad request' do - delete '/api/v1/profile/invalid', headers: headers - - expect(response).to have_http_status(400) - end - - it 'does not queue up an account update distribution' do - delete '/api/v1/profile/invalid', headers: headers - - expect(ActivityPub::UpdateDistributionWorker).to_not have_received(:perform_async).with(account.id) - end - end end end From 9ed0c91a3702557b80d490bbf43677114aec31f8 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 21 Aug 2023 16:09:26 +0200 Subject: [PATCH 11/52] Add auto-refresh of accounts we get new messages/edits of (#26510) --- app/lib/activitypub/activity/create.rb | 2 ++ app/lib/activitypub/activity/update.rb | 2 ++ app/models/account.rb | 8 ++++++++ app/workers/account_refresh_worker.rb | 14 ++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 app/workers/account_refresh_worker.rb diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 28cea8ec0..fedfa39de 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -4,6 +4,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity include FormattingHelper def perform + @account.schedule_refresh_if_stale! + dereference_object! case @object['type'] diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 8e72e0823..0a7762ee3 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -2,6 +2,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity def perform + @account.schedule_refresh_if_stale! + dereference_object! if equals_or_includes_any?(@object['type'], %w(Application Group Organization Person Service)) diff --git a/app/models/account.rb b/app/models/account.rb index 6b6cb8ee5..b1cb9eb5d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -63,6 +63,8 @@ class Account < ApplicationRecord trust_level ) + BACKGROUND_REFRESH_INTERVAL = 1.week.freeze + USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i MENTION_RE = %r{(?<=^|[^/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+} @@ -209,6 +211,12 @@ class Account < ApplicationRecord last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago end + def schedule_refresh_if_stale! + return unless last_webfingered_at.present? && last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago + + AccountRefreshWorker.perform_in(rand(6.hours.to_i), id) + end + def refresh! ResolveAccountService.new.call(acct) unless local? end diff --git a/app/workers/account_refresh_worker.rb b/app/workers/account_refresh_worker.rb new file mode 100644 index 000000000..08b5bab8d --- /dev/null +++ b/app/workers/account_refresh_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AccountRefreshWorker + include Sidekiq::Worker + + sidekiq_options queue: 'pull', retry: 3, dead: false, lock: :until_executed, lock_ttl: 1.day.to_i + + def perform(account_id) + account = Account.find_by(id: account_id) + return if account.nil? || account.last_webfingered_at > Account::BACKGROUND_REFRESH_INTERVAL.ago + + ResolveAccountService.new.call(account) + end +end From ac0eb0533eb97686b2dca6f31d0b87d8c6fd5c31 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 21 Aug 2023 16:50:22 +0200 Subject: [PATCH 12/52] Add Elasticsearch cluster health check and indexes mismatch check to dashboard (#26448) --- app/chewy/instances_index.rb | 2 +- .../admin/system_check/elasticsearch_check.rb | 61 ++++++++++++++++++- config/locales/en.yml | 14 +++++ .../system_check/elasticsearch_check_spec.rb | 35 ++++++++++- 4 files changed, 105 insertions(+), 7 deletions(-) diff --git a/app/chewy/instances_index.rb b/app/chewy/instances_index.rb index 0d58167dc..8f10d13b6 100644 --- a/app/chewy/instances_index.rb +++ b/app/chewy/instances_index.rb @@ -6,7 +6,7 @@ class InstancesIndex < Chewy::Index index_scope ::Instance.searchable root date_detection: false do - field :domain, type: 'text', index_prefixes: { min_chars: 1 } + field :domain, type: 'text', index_prefixes: { min_chars: 1, max_chars: 5 } field :accounts_count, type: 'long' end end diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb index 0b55be350..a6f1f164a 100644 --- a/app/lib/admin/system_check/elasticsearch_check.rb +++ b/app/lib/admin/system_check/elasticsearch_check.rb @@ -1,6 +1,13 @@ # frozen_string_literal: true class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck + INDEXES = [ + InstancesIndex, + AccountsIndex, + TagsIndex, + StatusesIndex, + ].freeze + def skip? !current_user.can?(:view_devops) end @@ -8,11 +15,15 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck def pass? return true unless Chewy.enabled? - running_version.present? && compatible_version? + running_version.present? && compatible_version? && cluster_health['status'] == 'green' && indexes_match? && preset_matches? + rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error + false end def message - if running_version.present? + if running_version.blank? + Admin::SystemCheck::Message.new(:elasticsearch_running_check) + elsif !compatible_version? Admin::SystemCheck::Message.new( :elasticsearch_version_check, I18n.t( @@ -21,13 +32,32 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck required_version: required_version ) ) + elsif !indexes_match? + Admin::SystemCheck::Message.new( + :elasticsearch_index_mismatch, + mismatched_indexes.join(' ') + ) + elsif cluster_health['status'] == 'red' + Admin::SystemCheck::Message.new(:elasticsearch_health_red) + elsif cluster_health['number_of_nodes'] < 2 && es_preset != 'single_node_cluster' + Admin::SystemCheck::Message.new(:elasticsearch_preset_single_node, nil, 'https://docs.joinmastodon.org/admin/optional/elasticsearch/#scaling') + elsif Chewy.client.indices.get_settings['chewy_specifications'].dig('settings', 'index', 'number_of_replicas')&.to_i&.positive? && es_preset == 'single_node_cluster' + Admin::SystemCheck::Message.new(:elasticsearch_reset_chewy) + elsif cluster_health['status'] == 'yellow' + Admin::SystemCheck::Message.new(:elasticsearch_health_yellow) else - Admin::SystemCheck::Message.new(:elasticsearch_running_check) + Admin::SystemCheck::Message.new(:elasticsearch_preset, nil, 'https://docs.joinmastodon.org/admin/optional/elasticsearch/#scaling') end + rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error + Admin::SystemCheck::Message.new(:elasticsearch_running_check) end private + def cluster_health + @cluster_health ||= Chewy.client.cluster.health + end + def running_version @running_version ||= begin Chewy.client.info['version']['number'] @@ -49,5 +79,30 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck Gem::Version.new(running_version) >= Gem::Version.new(required_version) || Gem::Version.new(compatible_wire_version) >= Gem::Version.new(required_version) + rescue ArgumentError + false + end + + def mismatched_indexes + @mismatched_indexes ||= INDEXES.filter_map do |klass| + klass.index_name if Chewy.client.indices.get_mapping[klass.index_name]&.deep_symbolize_keys != klass.mappings_hash + end + end + + def indexes_match? + mismatched_indexes.empty? + end + + def es_preset + ENV.fetch('ES_PRESET', 'single_node_cluster') + end + + def preset_matches? + case es_preset + when 'single_node_cluster' + cluster_health['number_of_nodes'] == 1 + else + cluster_health['number_of_nodes'] > 1 + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 4a052e000..389b7aa66 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -814,6 +814,20 @@ en: system_checks: database_schema_check: message_html: There are pending database migrations. Please run them to ensure the application behaves as expected + elasticsearch_health_red: + message_html: Elasticsearch cluster is unhealthy (red status), search features are unavailable + elasticsearch_health_yellow: + message_html: Elasticsearch cluster is unhealthy (yellow status), you may want to investigate the reason + elasticsearch_index_mismatch: + message_html: Elasticsearch index mappings are outdated. Please run tootctl search deploy --only=%{value} + elasticsearch_preset: + action: See documentation + message_html: Your Elasticsearch cluster has more than one node, but Mastodon is not configured to use them. + elasticsearch_preset_single_node: + action: See documentation + message_html: Your Elasticsearch cluster has only one node, ES_PRESET should be set to single_node_cluster. + elasticsearch_reset_chewy: + message_html: Your Elasticsearch system index is outdated due to a setting change. Please run tootctl search deploy --reset-chewy to update it. elasticsearch_running_check: message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search elasticsearch_version_check: diff --git a/spec/lib/admin/system_check/elasticsearch_check_spec.rb b/spec/lib/admin/system_check/elasticsearch_check_spec.rb index 498215926..bf518b56e 100644 --- a/spec/lib/admin/system_check/elasticsearch_check_spec.rb +++ b/spec/lib/admin/system_check/elasticsearch_check_spec.rb @@ -11,7 +11,25 @@ describe Admin::SystemCheck::ElasticsearchCheck do describe 'pass?' do context 'when chewy is enabled' do - before { allow(Chewy).to receive(:enabled?).and_return(true) } + before do + allow(Chewy).to receive(:enabled?).and_return(true) + allow(Chewy.client.cluster).to receive(:health).and_return({ 'status' => 'green', 'number_of_nodes' => 1 }) + allow(Chewy.client.indices).to receive(:get_mapping).and_return({ + AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys, + StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys, + InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys, + TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys, + }) + allow(Chewy.client.indices).to receive(:get_settings).and_return({ + 'chewy_specifications' => { + 'settings' => { + 'index' => { + 'number_of_replicas' => 0, + }, + }, + }, + }) + end context 'when running version is present and high enough' do before do @@ -67,8 +85,19 @@ describe Admin::SystemCheck::ElasticsearchCheck do end describe 'message' do + before do + allow(Chewy).to receive(:enabled?).and_return(true) + allow(Chewy.client.cluster).to receive(:health).and_return({ 'status' => 'green', 'number_of_nodes' => 1 }) + allow(Chewy.client.indices).to receive(:get_mapping).and_return({ + AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys, + StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys, + InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys, + TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys, + }) + end + context 'when running version is present' do - before { allow(Chewy.client).to receive(:info).and_return({ 'version' => { 'number' => '999.99.9' } }) } + before { allow(Chewy.client).to receive(:info).and_return({ 'version' => { 'number' => '1.2.3' } }) } it 'sends class name symbol to message instance' do allow(Admin::SystemCheck::Message).to receive(:new) @@ -77,7 +106,7 @@ describe Admin::SystemCheck::ElasticsearchCheck do check.message expect(Admin::SystemCheck::Message).to have_received(:new) - .with(:elasticsearch_version_check, 'Elasticsearch 999.99.9 is running while 7.x is required') + .with(:elasticsearch_version_check, 'Elasticsearch 1.2.3 is running while 7.x is required') end end From 061fd66ee66afc8a5c7923ff7648f51a6da3fe7c Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 21 Aug 2023 19:39:01 +0200 Subject: [PATCH 13/52] Remove hashtags from the last line of a status if it only contains hashtags (#26499) --- .../components/__tests__/hashtag_bar.tsx | 184 +++++++++++++++ .../mastodon/components/hashtag_bar.jsx | 50 ---- .../mastodon/components/hashtag_bar.tsx | 222 ++++++++++++++++++ app/javascript/mastodon/components/status.jsx | 7 +- .../mastodon/components/status_content.jsx | 14 +- .../status/components/detailed_status.jsx | 7 +- 6 files changed, 428 insertions(+), 56 deletions(-) create mode 100644 app/javascript/mastodon/components/__tests__/hashtag_bar.tsx delete mode 100644 app/javascript/mastodon/components/hashtag_bar.jsx create mode 100644 app/javascript/mastodon/components/hashtag_bar.tsx diff --git a/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx b/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx new file mode 100644 index 000000000..c7db485d0 --- /dev/null +++ b/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx @@ -0,0 +1,184 @@ +import { fromJS } from 'immutable'; + +import type { StatusLike } from '../hashtag_bar'; +import { computeHashtagBarForStatus } from '../hashtag_bar'; + +function createStatus( + content: string, + hashtags: string[], + hasMedia = false, + spoilerText?: string, +) { + return fromJS({ + tags: hashtags.map((name) => ({ name })), + contentHtml: content, + media_attachments: hasMedia ? ['fakeMedia'] : [], + spoiler_text: spoilerText, + }) as unknown as StatusLike; // need to force the type here, as it is not properly defined +} + +describe('computeHashtagBarForStatus', () => { + it('does nothing when there are no tags', () => { + const status = createStatus('

Simple text

', []); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Simple text

"`, + ); + }); + + it('displays out of band hashtags in the bar', () => { + const status = createStatus( + '

Simple text #hashtag

', + ['hashtag', 'test'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual(['test']); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Simple text #hashtag

"`, + ); + }); + + it('extract tags from the last line', () => { + const status = createStatus( + '

Simple text

#hashtag

', + ['hashtag'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual(['hashtag']); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Simple text

"`, + ); + }); + + it('does not include tags from content', () => { + const status = createStatus( + '

Simple text with a #hashtag

#hashtag

', + ['hashtag'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Simple text with a #hashtag

"`, + ); + }); + + it('works with one line status and hashtags', () => { + const status = createStatus( + '

#test. And another #hashtag

', + ['hashtag', 'test'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

#test. And another #hashtag

"`, + ); + }); + + it('de-duplicate accentuated characters with case differences', () => { + const status = createStatus( + '

Text

#éaa #Éaa

', + ['éaa'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual(['Éaa']); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Text

"`, + ); + }); + + it('does not display in bar a hashtag in content with a case difference', () => { + const status = createStatus( + '

Text #Éaa

#éaa

', + ['éaa'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Text #Éaa

"`, + ); + }); + + it('does not modify a status with a line of hashtags only', () => { + const status = createStatus( + '

#test #hashtag

', + ['test', 'hashtag'], + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

#test #hashtag

"`, + ); + }); + + it('puts the hashtags in the bar if a status content has hashtags in the only line and has a media', () => { + const status = createStatus( + '

This is my content! #hashtag

', + ['hashtag'], + true, + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

This is my content! #hashtag

"`, + ); + }); + + it('puts the hashtags in the bar if a status content is only hashtags and has a media', () => { + const status = createStatus( + '

#test #hashtag

', + ['test', 'hashtag'], + true, + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual(['test', 'hashtag']); + expect(statusContentProps.statusContent).toMatchInlineSnapshot(`""`); + }); + + it('does not use the hashtag bar if the status content is only hashtags, has a CW and a media', () => { + const status = createStatus( + '

#test #hashtag

', + ['test', 'hashtag'], + true, + 'My CW text', + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual([]); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

#test #hashtag

"`, + ); + }); +}); diff --git a/app/javascript/mastodon/components/hashtag_bar.jsx b/app/javascript/mastodon/components/hashtag_bar.jsx deleted file mode 100644 index 3c7e24228..000000000 --- a/app/javascript/mastodon/components/hashtag_bar.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import { useMemo, useState, useCallback } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const domParser = new DOMParser(); - -// About two lines on desktop -const VISIBLE_HASHTAGS = 7; - -export const HashtagBar = ({ hashtags, text }) => { - const renderedHashtags = useMemo(() => { - const body = domParser.parseFromString(text, 'text/html').documentElement; - return [].filter.call(body.querySelectorAll('a[href]'), link => link.textContent[0] === '#' || (link.previousSibling?.textContent?.[link.previousSibling.textContent.length - 1] === '#')).map(node => node.textContent); - }, [text]); - - const invisibleHashtags = useMemo(() => ( - hashtags.filter(hashtag => !renderedHashtags.some(textContent => textContent.localeCompare(`#${hashtag.get('name')}`, undefined, { sensitivity: 'accent' }) === 0 || textContent.localeCompare(hashtag.get('name'), undefined, { sensitivity: 'accent' }) === 0)) - ), [hashtags, renderedHashtags]); - - const [expanded, setExpanded] = useState(false); - const handleClick = useCallback(() => setExpanded(true), []); - - if (invisibleHashtags.isEmpty()) { - return null; - } - - const revealedHashtags = expanded ? invisibleHashtags : invisibleHashtags.take(VISIBLE_HASHTAGS); - - return ( -
- {revealedHashtags.map(hashtag => ( - - #{hashtag.get('name')} - - ))} - - {!expanded && invisibleHashtags.size > VISIBLE_HASHTAGS && } -
- ); -}; - -HashtagBar.propTypes = { - hashtags: ImmutablePropTypes.list, - text: PropTypes.string, -}; diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx new file mode 100644 index 000000000..8781c2663 --- /dev/null +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -0,0 +1,222 @@ +import { useState, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import type { List, Record } from 'immutable'; + +import { groupBy, minBy } from 'lodash'; + +import { getStatusContent } from './status_content'; + +// About two lines on desktop +const VISIBLE_HASHTAGS = 7; + +// Those types are not correct, they need to be replaced once this part of the state is typed +export type TagLike = Record<{ name: string }>; +export type StatusLike = Record<{ + tags: List; + contentHTML: string; + media_attachments: List; + spoiler_text?: string; +}>; + +function normalizeHashtag(hashtag: string) { + if (hashtag && hashtag.startsWith('#')) return hashtag.slice(1); + else return hashtag; +} + +function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { + return ( + element instanceof HTMLAnchorElement && + // it may be a starting with a hashtag + (element.textContent?.[0] === '#' || + // or a # + element.previousSibling?.textContent?.[ + element.previousSibling.textContent.length - 1 + ] === '#') + ); +} + +/** + * Removes duplicates from an hashtag list, case-insensitive, keeping only the best one + * "Best" here is defined by the one with the more casing difference (ie, the most camel-cased one) + * @param hashtags The list of hashtags + * @returns The input hashtags, but with only 1 occurence of each (case-insensitive) + */ +function uniqueHashtagsWithCaseHandling(hashtags: string[]) { + const groups = groupBy(hashtags, (tag) => + tag.normalize('NFKD').toLowerCase(), + ); + + return Object.values(groups).map((tags) => { + if (tags.length === 1) return tags[0]; + + // The best match is the one where we have the less difference between upper and lower case letter count + const best = minBy(tags, (tag) => { + const upperCase = Array.from(tag).reduce( + (acc, char) => (acc += char.toUpperCase() === char ? 1 : 0), + 0, + ); + + const lowerCase = tag.length - upperCase; + + return Math.abs(lowerCase - upperCase); + }); + + return best ?? tags[0]; + }); +} + +// Create the collator once, this is much more efficient +const collator = new Intl.Collator(undefined, { sensitivity: 'accent' }); +function localeAwareInclude(collection: string[], value: string) { + return collection.find((item) => collator.compare(item, value) === 0); +} + +// We use an intermediate function here to make it easier to test +export function computeHashtagBarForStatus(status: StatusLike): { + statusContentProps: { statusContent: string }; + hashtagsInBar: string[]; +} { + let statusContent = getStatusContent(status); + + const tagNames = status + .get('tags') + .map((tag) => tag.get('name')) + .toJS(); + + // this is returned if we stop the processing early, it does not change what is displayed + const defaultResult = { + statusContentProps: { statusContent }, + hashtagsInBar: [], + }; + + // return early if this status does not have any tags + if (tagNames.length === 0) return defaultResult; + + const template = document.createElement('template'); + template.innerHTML = statusContent.trim(); + + const lastChild = template.content.lastChild; + + if (!lastChild) return defaultResult; + + template.content.removeChild(lastChild); + const contentWithoutLastLine = template; + + // First, try to parse + const contentHashtags = Array.from( + contentWithoutLastLine.content.querySelectorAll('a[href]'), + ).reduce((result, link) => { + if (isNodeLinkHashtag(link)) { + if (link.textContent) result.push(normalizeHashtag(link.textContent)); + } + return result; + }, []); + + // Now we parse the last line, and try to see if it only contains hashtags + const lastLineHashtags: string[] = []; + // try to see if the last line is only hashtags + let onlyHashtags = true; + + Array.from(lastChild.childNodes).forEach((node) => { + if (isNodeLinkHashtag(node) && node.textContent) { + const normalized = normalizeHashtag(node.textContent); + + if (!localeAwareInclude(tagNames, normalized)) { + // stop here, this is not a real hashtag, so consider it as text + onlyHashtags = false; + return; + } + + if (!localeAwareInclude(contentHashtags, normalized)) + // only add it if it does not appear in the rest of the content + lastLineHashtags.push(normalized); + } else if (node.nodeType !== Node.TEXT_NODE || node.nodeValue?.trim()) { + // not a space + onlyHashtags = false; + } + }); + + const hashtagsInBar = tagNames.filter( + (tag) => + // the tag does not appear at all in the status content, it is an out-of-band tag + !localeAwareInclude(contentHashtags, tag) && + !localeAwareInclude(lastLineHashtags, tag), + ); + + const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0; + const hasMedia = status.get('media_attachments').size > 0; + const hasSpoiler = !!status.get('spoiler_text'); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- due to https://github.com/microsoft/TypeScript/issues/9998 + if (onlyHashtags && ((hasMedia && !hasSpoiler) || !isOnlyOneLine)) { + // if the last line only contains hashtags, and we either: + // - have other content in the status + // - dont have other content, but a media and no CW. If it has a CW, then we do not remove the content to avoid having an empty content behind the CW button + statusContent = contentWithoutLastLine.innerHTML; + // and add the tags to the bar + hashtagsInBar.push(...lastLineHashtags); + } + + return { + statusContentProps: { statusContent }, + hashtagsInBar: uniqueHashtagsWithCaseHandling(hashtagsInBar), + }; +} + +/** + * This function will process a status to, at the same time (avoiding parsing it twice): + * - build the HashtagBar for this status + * - remove the last-line hashtags from the status content + * @param status The status to process + * @returns Props to be passed to the component, and the hashtagBar to render + */ +export function getHashtagBarForStatus(status: StatusLike) { + const { statusContentProps, hashtagsInBar } = + computeHashtagBarForStatus(status); + + return { + statusContentProps, + hashtagBar: , + }; +} + +const HashtagBar: React.FC<{ + hashtags: string[]; +}> = ({ hashtags }) => { + const [expanded, setExpanded] = useState(false); + const handleClick = useCallback(() => { + setExpanded(true); + }, []); + + if (hashtags.length === 0) { + return null; + } + + const revealedHashtags = expanded + ? hashtags + : hashtags.slice(0, VISIBLE_HASHTAGS - 1); + + return ( +
+ {revealedHashtags.map((hashtag) => ( + + #{hashtag} + + ))} + + {!expanded && hashtags.length > VISIBLE_HASHTAGS && ( + + )} +
+ ); +}; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 7c34684d7..45a2106db 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -22,7 +22,7 @@ import { displayMedia } from '../initial_state'; import { Avatar } from './avatar'; import { AvatarOverlay } from './avatar_overlay'; import { DisplayName } from './display_name'; -import { HashtagBar } from './hashtag_bar'; +import { getHashtagBarForStatus } from './hashtag_bar'; import { RelativeTimestamp } from './relative_timestamp'; import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; @@ -545,6 +545,8 @@ class Status extends ImmutablePureComponent { const visibilityIcon = visibilityIconInfo[status.get('visibility')]; + const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); + return (
@@ -577,11 +579,12 @@ class Status extends ImmutablePureComponent { onTranslate={this.handleTranslate} collapsible onCollapsedToggle={this.handleCollapsedToggle} + {...statusContentProps} /> {media} - + {hashtagBar}
diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 84a698810..d3bbc1ba0 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -15,6 +15,15 @@ import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_s const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) +/** + * + * @param {any} status + * @returns {string} + */ +export function getStatusContent(status) { + return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml'); +} + class TranslateButton extends PureComponent { static propTypes = { @@ -65,6 +74,7 @@ class StatusContent extends PureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, + statusContent: PropTypes.string, expanded: PropTypes.bool, onExpandedToggle: PropTypes.func, onTranslate: PropTypes.func, @@ -225,7 +235,7 @@ class StatusContent extends PureComponent { }; render () { - const { status, intl } = this.props; + const { status, intl, statusContent } = this.props; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const renderReadMore = this.props.onClick && status.get('collapsed'); @@ -233,7 +243,7 @@ class StatusContent extends PureComponent { const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); - const content = { __html: status.getIn(['translation', 'contentHtml']) || status.get('contentHtml') }; + const content = { __html: statusContent ?? getStatusContent(status) }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; const language = status.getIn(['translation', 'language']) || status.get('language'); const classNames = classnames('status__content', { diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index c1815b916..401550e49 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -10,7 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { AnimatedNumber } from 'mastodon/components/animated_number'; import EditedTimestamp from 'mastodon/components/edited_timestamp'; -import { HashtagBar } from 'mastodon/components/hashtag_bar'; +import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar'; import { Icon } from 'mastodon/components/icon'; import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; @@ -292,6 +292,8 @@ class DetailedStatus extends ImmutablePureComponent { ); } + const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); + return (
@@ -311,11 +313,12 @@ class DetailedStatus extends ImmutablePureComponent { expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} onTranslate={this.handleTranslate} + {...statusContentProps} /> {media} - + {hashtagBar}
From facfec1ba36cee27f232ebff90b990933719235a Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 21 Aug 2023 20:18:49 +0200 Subject: [PATCH 14/52] Bump version to v4.2.0-beta2 (#26579) --- CHANGELOG.md | 33 ++++++++++++++++++++++++++------- lib/mastodon/version.rb | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfcc18836..107dfaca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The following changelog entries focus on changes visible to users, administrator ### Added +- **Add “Privacy and reach” tab in profile settings** ([Gargron](https://github.com/mastodon/mastodon/pull/26484), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26508)) + This reorganized scattered privacy and reach settings to a single place, as well as improve their wording. +- **Add display of out-of-band hashtags in the web interface** ([Gargron](https://github.com/mastodon/mastodon/pull/26492), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26497), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26506), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26525)) - **Add role badges to the web interface** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25649), [Gargron](https://github.com/mastodon/mastodon/pull/26281)) - **Add ability to pick domains to forward reports to using the `forward_to_domains` parameter in `POST /api/v1/reports`** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25866)) The `forward_to_domains` REST API parameter is a list of strings. If it is empty or omitted, the previous behavior is maintained. @@ -23,8 +26,18 @@ The following changelog entries focus on changes visible to users, administrator - **Add optional hCaptcha support** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25019), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25057), [Gargron](https://github.com/mastodon/mastodon/pull/25395), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26388)) - **Add lines to threads in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24549), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24677), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24696), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24711), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24713), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24715), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24800), [teeerevor](https://github.com/mastodon/mastodon/pull/25706), [renchap](https://github.com/mastodon/mastodon/pull/25807)) - **Add new onboarding flow to web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24619), [Gargron](https://github.com/mastodon/mastodon/pull/24646), [Gargron](https://github.com/mastodon/mastodon/pull/24705), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24872), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24883), [Gargron](https://github.com/mastodon/mastodon/pull/24954), [stevenjlm](https://github.com/mastodon/mastodon/pull/24959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25010), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25275), [Gargron](https://github.com/mastodon/mastodon/pull/25559), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25561)) +- **Add `S3_DISABLE_CHECKSUM_MODE` environment variable for compatibility with some S3-compatible providers** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26435)) +- **Add auto-refresh of accounts we get new messages/edits of** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26510)) +- **Add Elasticsearch cluster health check and indexes mismatch check to dashboard** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26448)) +- Add support for `indexable` attribute on remote actors ([Gargron](https://github.com/mastodon/mastodon/pull/26485)) +- Add `DELETE /api/v1/profile/avatar` and `DELETE /api/v1/profile/header` to the REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25124), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26573)) +- Add `ES_PRESET` option to customize numbers of shards and replicas ([Gargron](https://github.com/mastodon/mastodon/pull/26483), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26489)) + This can have a value of `single_node_cluster` (default), `small_cluster` (uses one replica) or `large_cluster` (uses one replica and a higher number of shards). +- Add missing `instances` option to `tootctl search deploy` ([tribela](https://github.com/mastodon/mastodon/pull/26461)) +- Add `CACHE_BUSTER_HTTP_METHOD` environment variable ([renchap](https://github.com/mastodon/mastodon/pull/26528), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26542)) +- Add support for `DB_PASS` when using `DATABASE_URL` ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26295)) - Add `GET /api/v1/instance/languages` to REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24443)) -- Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384)) +- Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26447)) - Add client-side timeout on resend confirmation button ([Gargron](https://github.com/mastodon/mastodon/pull/26300)) - Add published date and author to news on the explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26155)) - Add `lang` attribute to various UI components ([c960657](https://github.com/mastodon/mastodon/pull/23869), [c960657](https://github.com/mastodon/mastodon/pull/23891), [c960657](https://github.com/mastodon/mastodon/pull/26111), [c960657](https://github.com/mastodon/mastodon/pull/26149)) @@ -43,7 +56,7 @@ The following changelog entries focus on changes visible to users, administrator - Add unsubscribe link and headers to e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/25378), [c960657](https://github.com/mastodon/mastodon/pull/26085)) - Add logging of websocket send errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25280)) - Add time zone preference ([Gargron](https://github.com/mastodon/mastodon/pull/25342), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26025)) -- Add `legal` as report category ([Gargron](https://github.com/mastodon/mastodon/pull/23941), [renchap](https://github.com/mastodon/mastodon/pull/25400)) +- Add `legal` as report category ([Gargron](https://github.com/mastodon/mastodon/pull/23941), [renchap](https://github.com/mastodon/mastodon/pull/25400), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26509)) - Add `data-nosnippet` so Google doesn't use trending posts in snippets for `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25279)) - Add card with who invited you to join when displaying rules on sign-up ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23475)) - Add missing primary keys to `accounts_tags` and `statuses_tags` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25210)) @@ -80,11 +93,12 @@ The following changelog entries focus on changes visible to users, administrator ### Changed +- **Change hashtags to be displayed separately when they are the last line of a post** ([renchap](https://github.com/mastodon/mastodon/pull/26499)) - **Change reblogs to be excluded from "Posts and replies" tab in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26302)) -- **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267)) +- **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267), [mgmn](https://github.com/mastodon/mastodon/pull/26459)) - **Change design of link previews in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26136), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26151), [Gargron](https://github.com/mastodon/mastodon/pull/26153), [Gargron](https://github.com/mastodon/mastodon/pull/26250), [Gargron](https://github.com/mastodon/mastodon/pull/26287), [Gargron](https://github.com/mastodon/mastodon/pull/26286), [c960657](https://github.com/mastodon/mastodon/pull/26184)) - **Change "direct message" nomenclature to "private mention" in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24248)) -- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168)) +- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26452)) - **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378)) - **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874)) - **Change local and federated timelines to be in a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247)) @@ -97,7 +111,9 @@ The following changelog entries focus on changes visible to users, administrator - **Change replica support to native Rails adapter** ([krainboltgreene](https://github.com/mastodon/mastodon/pull/25693), [Gargron](https://github.com/mastodon/mastodon/pull/25849), [Gargron](https://github.com/mastodon/mastodon/pull/25874), [Gargron](https://github.com/mastodon/mastodon/pull/25851), [Gargron](https://github.com/mastodon/mastodon/pull/25977), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26074), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26386)) This is a breaking change, dropping `makara` support, and requiring you to update your database configuration if you are using replicas. To tell Mastodon to use a read replica, you can either set the `REPLICA_DB_NAME` environment variable (along with `REPLICA_DB_USER`, `REPLICA_DB_PASS`, `REPLICA_DB_HOST`, and `REPLICA_DB_PORT`, if they differ from the primary database), or the `REPLICA_DATABASE_URL` environment variable if your configuration is based on `DATABASE_URL`. -- Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362)) +- Change follow recommendation materialized view to be faster in most cases ([renchap, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26545)) +- Change `robots.txt` to block GPTBot ([Foritus](https://github.com/mastodon/mastodon/pull/26396)) +- Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26416)) - Change streaming `/metrics` to include additional metrics ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26299)) - Change indexing frequency from 5 minutes to 1 minute, add locks to schedulers ([Gargron](https://github.com/mastodon/mastodon/pull/26304)) - Change column link to add a better keyboard focus indicator ([teeerevor](https://github.com/mastodon/mastodon/pull/26278)) @@ -114,7 +130,7 @@ The following changelog entries focus on changes visible to users, administrator - Change header backgrounds to use fewer different colors in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25577)) - Change files to be deleted in batches instead of one-by-one ([Gargron](https://github.com/mastodon/mastodon/pull/23302), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25586), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25587)) - Change emoji picker icon ([iparr](https://github.com/mastodon/mastodon/pull/25479)) -- Change edit profile page ([Gargron](https://github.com/mastodon/mastodon/pull/25413)) +- Change edit profile page ([Gargron](https://github.com/mastodon/mastodon/pull/25413), [c960657](https://github.com/mastodon/mastodon/pull/26538)) - Change "bot" label to "automated" ([Gargron](https://github.com/mastodon/mastodon/pull/25356)) - Change design of dropdowns in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25107)) - Change wording of “Content cache retention period” setting to highlight destructive implications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23261)) @@ -172,6 +188,9 @@ The following changelog entries focus on changes visible to users, administrator - **Fix being unable to load past a full page of filtered posts in Home timeline** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24930)) - **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073)) - **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218)) +- **Fix blocking subdomains of an already-blocked domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26392)) +- Fix uploading of video files for which `ffprobe` reports `0/0` average framerate ([NicolaiSoeborg](https://github.com/mastodon/mastodon/pull/26500)) +- Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409)) - Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375)) - Fix light theme select option for hashtags ([teeerevor](https://github.com/mastodon/mastodon/pull/26311)) - Fix AVIF attachments ([c960657](https://github.com/mastodon/mastodon/pull/26264)) @@ -189,7 +208,7 @@ The following changelog entries focus on changes visible to users, administrator - Fix for "follows you" indicator in light web UI not readable ([vmstan](https://github.com/mastodon/mastodon/pull/25993)) - Fix incorrect line break between icon and number of reposts & favourites ([edent](https://github.com/mastodon/mastodon/pull/26004)) - Fix sounds not being loaded from assets host ([Signez](https://github.com/mastodon/mastodon/pull/25931)) -- Fix buttons showing inconsistent styles ([teeerevor](https://github.com/mastodon/mastodon/pull/25903), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25965), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26341)) +- Fix buttons showing inconsistent styles ([teeerevor](https://github.com/mastodon/mastodon/pull/25903), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25965), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26482)) - Fix trend calculation working on too many items at a time ([Gargron](https://github.com/mastodon/mastodon/pull/25835)) - Fix dropdowns being disabled for logged out users in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25964)) - Fix explore page being inaccessible when opted-out of trends in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25716)) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 2b2a5d118..ee81eedaa 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -17,7 +17,7 @@ module Mastodon end def flags - ENV.fetch('MASTODON_VERSION_FLAGS', '-beta1') + ENV.fetch('MASTODON_VERSION_FLAGS', '-beta2') end def suffix From fe31571965a6b261165342ec97d11d67f0fae12b Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 22 Aug 2023 09:11:59 +0200 Subject: [PATCH 15/52] Fix layout of the closed registrations modal (#26593) --- .../styles/mastodon/components.scss | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 115b6f5d1..7662f3d9b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -8539,6 +8539,44 @@ noscript { } } + &__choices { + display: flex; + gap: 40px; + + &__choice { + flex: 1; + box-sizing: border-box; + + h3 { + margin-bottom: 20px; + } + + p { + color: $darker-text-color; + margin-bottom: 20px; + font-size: 15px; + } + + .button { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + } + } + + @media screen and (max-width: $no-gap-breakpoint - 1px) { + &__choices { + flex-direction: column; + + &__choice { + margin-top: 40px; + } + } + } + .link-button { font-size: inherit; display: inline; From b970ed60983ef9f5a8cb9a71836737442d155bc5 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 22 Aug 2023 03:31:40 -0400 Subject: [PATCH 16/52] Update rubocop and rubocop-rspec (#26329) --- .rubocop_todo.yml | 56 ++++++++----------- Gemfile.lock | 10 ++-- app/lib/importer/base_importer.rb | 4 +- lib/redis/namespace_extensions.rb | 4 +- .../translation_languages_controller_spec.rb | 3 +- .../statuses/translations_controller_spec.rb | 3 +- spec/helpers/admin/filter_helper_spec.rb | 3 +- spec/helpers/application_helper_spec.rb | 4 +- spec/helpers/home_helper_spec.rb | 10 +--- .../system_check/elasticsearch_check_spec.rb | 5 +- spec/models/report_filter_spec.rb | 3 +- spec/services/suspend_account_service_spec.rb | 3 +- .../services/translate_status_service_spec.rb | 3 +- .../unsuspend_account_service_spec.rb | 3 +- spec/views/statuses/show.html.haml_spec.rb | 8 +-- 15 files changed, 48 insertions(+), 74 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 92c7f9cdc..945d7514a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.54.2. +# using RuboCop version 1.56.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -61,38 +61,8 @@ Lint/EmptyBlock: - 'spec/fabricators/access_token_fabricator.rb' - 'spec/fabricators/conversation_fabricator.rb' - 'spec/fabricators/system_key_fabricator.rb' - - 'spec/helpers/admin/action_logs_helper_spec.rb' - 'spec/lib/activitypub/adapter_spec.rb' - - 'spec/models/account_alias_spec.rb' - - 'spec/models/account_deletion_request_spec.rb' - - 'spec/models/account_moderation_note_spec.rb' - - 'spec/models/announcement_mute_spec.rb' - - 'spec/models/announcement_reaction_spec.rb' - - 'spec/models/announcement_spec.rb' - - 'spec/models/backup_spec.rb' - - 'spec/models/conversation_mute_spec.rb' - - 'spec/models/custom_filter_keyword_spec.rb' - - 'spec/models/custom_filter_spec.rb' - - 'spec/models/device_spec.rb' - - 'spec/models/encrypted_message_spec.rb' - - 'spec/models/featured_tag_spec.rb' - - 'spec/models/follow_recommendation_suppression_spec.rb' - - 'spec/models/list_account_spec.rb' - - 'spec/models/list_spec.rb' - - 'spec/models/login_activity_spec.rb' - - 'spec/models/mute_spec.rb' - - 'spec/models/preview_card_spec.rb' - - 'spec/models/preview_card_trend_spec.rb' - - 'spec/models/relay_spec.rb' - - 'spec/models/scheduled_status_spec.rb' - - 'spec/models/status_stat_spec.rb' - - 'spec/models/status_trend_spec.rb' - - 'spec/models/system_key_spec.rb' - - 'spec/models/tag_follow_spec.rb' - - 'spec/models/unavailable_domain_spec.rb' - - 'spec/models/user_invite_request_spec.rb' - 'spec/models/user_role_spec.rb' - - 'spec/models/web/setting_spec.rb' Lint/NonLocalExitFromIterator: Exclude: @@ -135,7 +105,7 @@ Lint/UselessAssignment: # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 146 + Max: 144 # Configuration parameters: CountBlocks, Max. Metrics/BlockNesting: @@ -164,6 +134,19 @@ Naming/VariableNumber: - 'spec/models/domain_block_spec.rb' - 'spec/models/user_spec.rb' +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: SafeMultiline. +Performance/DeletePrefix: + Exclude: + - 'app/models/featured_tag.rb' + +Performance/MapMethodChain: + Exclude: + - 'app/models/feed.rb' + - 'lib/mastodon/cli/maintenance.rb' + - 'spec/services/bulk_import_service_spec.rb' + - 'spec/services/import_service_spec.rb' + RSpec/AnyInstance: Exclude: - 'spec/controllers/activitypub/inboxes_controller_spec.rb' @@ -762,6 +745,15 @@ Style/RedundantFetchBlock: - 'config/initializers/paperclip.rb' - 'config/puma.rb' +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'app/controllers/api/v1/directories_controller.rb' + - 'app/controllers/auth/confirmations_controller.rb' + - 'app/lib/ostatus/tag_manager.rb' + - 'app/models/form/import.rb' + # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! diff --git a/Gemfile.lock b/Gemfile.lock index 73e3bd975..e92079c1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -147,6 +147,7 @@ GEM faraday_middleware (~> 1.0, >= 1.0.0.rc1) net-http-persistent (~> 4.0) nokogiri (~> 1, >= 1.10.8) + base64 (0.1.1) bcrypt (3.1.18) better_errors (2.10.1) erubi (>= 1.0.0) @@ -637,7 +638,8 @@ GEM sidekiq (>= 2.4.0) rspec-support (3.12.0) rspec_chunked (0.6) - rubocop (1.54.2) + rubocop (1.56.1) + base64 (~> 0.1.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -645,7 +647,7 @@ GEM rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.0) @@ -654,14 +656,14 @@ GEM rubocop (~> 1.41) rubocop-factory_bot (2.23.1) rubocop (~> 1.33) - rubocop-performance (1.18.0) + rubocop-performance (1.19.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.20.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - rubocop-rspec (2.22.0) + rubocop-rspec (2.23.2) rubocop (~> 1.33) rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) diff --git a/app/lib/importer/base_importer.rb b/app/lib/importer/base_importer.rb index 07be4650e..cc1b7b44d 100644 --- a/app/lib/importer/base_importer.rb +++ b/app/lib/importer/base_importer.rb @@ -68,8 +68,8 @@ class Importer::BaseImporter protected - def in_work_unit(*args, &block) - work_unit = Concurrent::Promises.future_on(@executor, *args, &block) + def in_work_unit(...) + work_unit = Concurrent::Promises.future_on(@executor, ...) work_unit.on_fulfillment!(&@on_progress) work_unit.on_rejection!(&@on_failure) diff --git a/lib/redis/namespace_extensions.rb b/lib/redis/namespace_extensions.rb index 310a4f465..9af59c296 100644 --- a/lib/redis/namespace_extensions.rb +++ b/lib/redis/namespace_extensions.rb @@ -2,8 +2,8 @@ class Redis module NamespaceExtensions - def exists?(*args, &block) - call_with_namespace('exists?', *args, &block) + def exists?(...) + call_with_namespace('exists?', ...) end end end diff --git a/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb b/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb index 5b7e4abb6..88bcc4034 100644 --- a/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb +++ b/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb @@ -16,8 +16,7 @@ describe Api::V1::Instances::TranslationLanguagesController do context 'when a translation service is configured' do before do service = instance_double(TranslationService::DeepL, languages: { nil => %w(en de), 'en' => ['de'] }) - allow(TranslationService).to receive(:configured?).and_return(true) - allow(TranslationService).to receive(:configured).and_return(service) + allow(TranslationService).to receive_messages(configured?: true, configured: service) end it 'returns language matrix' do diff --git a/spec/controllers/api/v1/statuses/translations_controller_spec.rb b/spec/controllers/api/v1/statuses/translations_controller_spec.rb index 989e94750..6257494ae 100644 --- a/spec/controllers/api/v1/statuses/translations_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/translations_controller_spec.rb @@ -20,8 +20,7 @@ describe Api::V1::Statuses::TranslationsController do before do translation = TranslationService::Translation.new(text: 'Hello') service = instance_double(TranslationService::DeepL, translate: [translation]) - allow(TranslationService).to receive(:configured?).and_return(true) - allow(TranslationService).to receive(:configured).and_return(service) + allow(TranslationService).to receive_messages(configured?: true, configured: service) Rails.cache.write('translation_service/languages', { 'es' => ['en'] }) post :create, params: { status_id: status.id } end diff --git a/spec/helpers/admin/filter_helper_spec.rb b/spec/helpers/admin/filter_helper_spec.rb index bbf90a996..40ed63239 100644 --- a/spec/helpers/admin/filter_helper_spec.rb +++ b/spec/helpers/admin/filter_helper_spec.rb @@ -7,8 +7,7 @@ describe Admin::FilterHelper do params = ActionController::Parameters.new( { test: 'test' } ) - allow(helper).to receive(:params).and_return(params) - allow(helper).to receive(:url_for).and_return('/test') + allow(helper).to receive_messages(params: params, url_for: '/test') result = helper.filter_link_to('text', { resolved: true }) expect(result).to match(/text/) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index d0b2900d6..b87d06359 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -31,9 +31,7 @@ describe ApplicationHelper do context 'with a body class string from a controller' do before do without_partial_double_verification do - allow(helper).to receive(:body_class_string).and_return('modal-layout compose-standalone') - allow(helper).to receive(:current_theme).and_return('default') - allow(helper).to receive(:current_account).and_return(Fabricate(:account)) + allow(helper).to receive_messages(body_class_string: 'modal-layout compose-standalone', current_theme: 'default', current_account: Fabricate(:account)) end end diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index 15067471e..c6baec5a1 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -25,8 +25,7 @@ RSpec.describe HomeHelper do it 'returns a link to the account' do without_partial_double_verification do - allow(helper).to receive(:current_account).and_return(account) - allow(helper).to receive(:prefers_autoplay?).and_return(false) + allow(helper).to receive_messages(current_account: account, prefers_autoplay?: false) result = helper.account_link_to(account) expect(result).to match "@#{account.acct}" @@ -101,8 +100,7 @@ RSpec.describe HomeHelper do context 'with open registrations' do it 'returns correct sign up message' do - allow(helper).to receive(:closed_registrations?).and_return(false) - allow(helper).to receive(:open_registrations?).and_return(true) + allow(helper).to receive_messages(closed_registrations?: false, open_registrations?: true) result = helper.sign_up_message expect(result).to eq t('auth.register') @@ -111,9 +109,7 @@ RSpec.describe HomeHelper do context 'with approved registrations' do it 'returns correct sign up message' do - allow(helper).to receive(:closed_registrations?).and_return(false) - allow(helper).to receive(:open_registrations?).and_return(false) - allow(helper).to receive(:approved_registrations?).and_return(true) + allow(helper).to receive_messages(closed_registrations?: false, open_registrations?: false, approved_registrations?: true) result = helper.sign_up_message expect(result).to eq t('auth.apply_for_account') diff --git a/spec/lib/admin/system_check/elasticsearch_check_spec.rb b/spec/lib/admin/system_check/elasticsearch_check_spec.rb index bf518b56e..f3918d403 100644 --- a/spec/lib/admin/system_check/elasticsearch_check_spec.rb +++ b/spec/lib/admin/system_check/elasticsearch_check_spec.rb @@ -14,13 +14,12 @@ describe Admin::SystemCheck::ElasticsearchCheck do before do allow(Chewy).to receive(:enabled?).and_return(true) allow(Chewy.client.cluster).to receive(:health).and_return({ 'status' => 'green', 'number_of_nodes' => 1 }) - allow(Chewy.client.indices).to receive(:get_mapping).and_return({ + allow(Chewy.client.indices).to receive_messages(get_mapping: { AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys, StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys, InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys, TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys, - }) - allow(Chewy.client.indices).to receive(:get_settings).and_return({ + }, get_settings: { 'chewy_specifications' => { 'settings' => { 'index' => { diff --git a/spec/models/report_filter_spec.rb b/spec/models/report_filter_spec.rb index 4b0852f08..6baf0ea42 100644 --- a/spec/models/report_filter_spec.rb +++ b/spec/models/report_filter_spec.rb @@ -23,8 +23,7 @@ describe ReportFilter do it 'combines filters on Report' do filter = described_class.new(account_id: '123', resolved: true, target_account_id: '456') - allow(Report).to receive(:where).and_return(Report.none) - allow(Report).to receive(:resolved).and_return(Report.none) + allow(Report).to receive_messages(where: Report.none, resolved: Report.none) filter.results expect(Report).to have_received(:where).with(account_id: '123') expect(Report).to have_received(:where).with(target_account_id: '456') diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb index 7cdbc873e..edb705008 100644 --- a/spec/services/suspend_account_service_spec.rb +++ b/spec/services/suspend_account_service_spec.rb @@ -10,8 +10,7 @@ RSpec.describe SuspendAccountService, type: :service do let!(:list) { Fabricate(:list, account: local_follower) } before do - allow(FeedManager.instance).to receive(:unmerge_from_home).and_return(nil) - allow(FeedManager.instance).to receive(:unmerge_from_list).and_return(nil) + allow(FeedManager.instance).to receive_messages(unmerge_from_home: nil, unmerge_from_list: nil) local_follower.follow!(account) list.accounts << account diff --git a/spec/services/translate_status_service_spec.rb b/spec/services/translate_status_service_spec.rb index 515dd1a99..5f6418f5d 100644 --- a/spec/services/translate_status_service_spec.rb +++ b/spec/services/translate_status_service_spec.rb @@ -29,8 +29,7 @@ RSpec.describe TranslateStatusService, type: :service do end end - allow(TranslationService).to receive(:configured?).and_return(true) - allow(TranslationService).to receive(:configured).and_return(translation_service) + allow(TranslationService).to receive_messages(configured?: true, configured: translation_service) end it 'returns translated status content' do diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb index 3c13d5bd3..c555b661e 100644 --- a/spec/services/unsuspend_account_service_spec.rb +++ b/spec/services/unsuspend_account_service_spec.rb @@ -10,8 +10,7 @@ RSpec.describe UnsuspendAccountService, type: :service do let!(:list) { Fabricate(:list, account: local_follower) } before do - allow(FeedManager.instance).to receive(:merge_into_home).and_return(nil) - allow(FeedManager.instance).to receive(:merge_into_list).and_return(nil) + allow(FeedManager.instance).to receive_messages(merge_into_home: nil, merge_into_list: nil) local_follower.follow!(account) list.accounts << account diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb index 06f5132d9..354f9d3e6 100644 --- a/spec/views/statuses/show.html.haml_spec.rb +++ b/spec/views/statuses/show.html.haml_spec.rb @@ -4,15 +4,9 @@ require 'rails_helper' describe 'statuses/show.html.haml', without_verify_partial_doubles: true do before do - allow(view).to receive(:api_oembed_url).and_return('') - allow(view).to receive(:show_landing_strip?).and_return(true) - allow(view).to receive(:site_title).and_return('example site') - allow(view).to receive(:site_hostname).and_return('example.com') - allow(view).to receive(:full_asset_url).and_return('//asset.host/image.svg') + allow(view).to receive_messages(api_oembed_url: '', show_landing_strip?: true, site_title: 'example site', site_hostname: 'example.com', full_asset_url: '//asset.host/image.svg', current_account: nil, single_user_mode?: false) allow(view).to receive(:local_time) allow(view).to receive(:local_time_ago) - allow(view).to receive(:current_account).and_return(nil) - allow(view).to receive(:single_user_mode?).and_return(false) assign(:instance_presenter, InstancePresenter.new) end From bb2db2aec06011671ae492aa004eed4743b1e1ac Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Tue, 22 Aug 2023 13:24:16 +0200 Subject: [PATCH 17/52] Add `circular-dependency-plugin` to detect any circular deps issues (#26600) --- config/webpack/shared.js | 4 ++++ package.json | 1 + yarn.lock | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/config/webpack/shared.js b/config/webpack/shared.js index 3b69282d5..7c00d85d3 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -2,6 +2,7 @@ const { basename, dirname, join, relative, resolve } = require('path'); +const CircularDependencyPlugin = require('circular-dependency-plugin'); const { sync } = require('glob'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const extname = require('path-complete-extname'); @@ -84,6 +85,9 @@ module.exports = { writeToDisk: true, publicPath: true, }), + new CircularDependencyPlugin({ + failOnError: true, + }) ], resolve: { diff --git a/package.json b/package.json index 0dbd84ee8..16c5d529a 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "babel-plugin-preval": "^5.1.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "blurhash": "^2.0.5", + "circular-dependency-plugin": "^5.2.2", "classnames": "^2.3.2", "cocoon-js-vanilla": "^1.3.0", "color-blend": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 65a0eb036..779b3a21b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3998,6 +3998,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-dependency-plugin@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" + integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== + cjs-module-lexer@^1.0.0: version "1.2.3" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" @@ -11281,6 +11286,7 @@ stringz@^2.1.0: char-regex "^1.0.2" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== From 3249c06c7327101b0492a3106d970f88f87a1d96 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Tue, 22 Aug 2023 16:47:19 +0200 Subject: [PATCH 18/52] Update `SECURITY.md` to indicate issues can be reported on Github (#26599) --- SECURITY.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 6a51c126a..7a79d9f91 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,11 @@ # Security Policy -If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at . +If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either: -You should _not_ report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. +- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new) +- reach us at + +You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. ## Scope From dc09c10fa8cc9230bf14e48d790c8f0c26043f8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 16:47:40 +0200 Subject: [PATCH 19/52] Update dependency mime-types to v3.5.1 (#26595) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e92079c1a..706df8ffd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -452,7 +452,7 @@ GEM hashie (~> 5.0) memory_profiler (1.0.1) method_source (1.0.0) - mime-types (3.5.0) + mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.0808) mini_mime (1.1.5) From c01ecd0879c00849138b49dedffcbcb644ddb799 Mon Sep 17 00:00:00 2001 From: gunchleoc Date: Tue, 22 Aug 2023 16:53:27 +0200 Subject: [PATCH 20/52] Add cherokee to languages dropdown (#26012) --- app/helpers/languages_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index 1f400b477..1256932fd 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -188,6 +188,7 @@ module LanguagesHelper ISO_639_3 = { ast: ['Asturian', 'Asturianu'].freeze, + chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze, ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze, cnr: ['Montenegrin', 'crnogorski'].freeze, jbo: ['Lojban', 'la .lojban.'].freeze, From 25dc01660d5cf996fa9ba618803327a8f5069f57 Mon Sep 17 00:00:00 2001 From: gunchleoc Date: Tue, 22 Aug 2023 17:50:04 +0200 Subject: [PATCH 21/52] Add Kalmyk to languages dropdown (#26013) --- app/helpers/languages_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index 1256932fd..e76def581 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -201,6 +201,7 @@ module LanguagesHelper smj: ['Lule Sami', 'Julevsámegiella'].freeze, szl: ['Silesian', 'ślůnsko godka'].freeze, tok: ['Toki Pona', 'toki pona'].freeze, + xal: ['Kalmyk', 'Хальмг келн'].freeze, zba: ['Balaibalan', 'باليبلن'].freeze, zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze, }.freeze From bd023a2637ce4df2102c4f760ca1d05ff2302d9f Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 22 Aug 2023 18:51:32 +0200 Subject: [PATCH 22/52] Fix admin dashboard check when using Elasticsearch with `ES_PREFIX` (#26605) --- app/lib/admin/system_check/elasticsearch_check.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb index a6f1f164a..91070756c 100644 --- a/app/lib/admin/system_check/elasticsearch_check.rb +++ b/app/lib/admin/system_check/elasticsearch_check.rb @@ -41,7 +41,7 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck Admin::SystemCheck::Message.new(:elasticsearch_health_red) elsif cluster_health['number_of_nodes'] < 2 && es_preset != 'single_node_cluster' Admin::SystemCheck::Message.new(:elasticsearch_preset_single_node, nil, 'https://docs.joinmastodon.org/admin/optional/elasticsearch/#scaling') - elsif Chewy.client.indices.get_settings['chewy_specifications'].dig('settings', 'index', 'number_of_replicas')&.to_i&.positive? && es_preset == 'single_node_cluster' + elsif Chewy.client.indices.get_settings[Chewy::Stash::Specification.index_name]&.dig('settings', 'index', 'number_of_replicas')&.to_i&.positive? && es_preset == 'single_node_cluster' Admin::SystemCheck::Message.new(:elasticsearch_reset_chewy) elsif cluster_health['status'] == 'yellow' Admin::SystemCheck::Message.new(:elasticsearch_health_yellow) From 58acaa9ae666d4e40c5b68316513a0fcb9200daf Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 23 Aug 2023 08:18:07 +0200 Subject: [PATCH 23/52] Better hashtag normalization when processing a post (#26614) --- .../components/__tests__/hashtag_bar.tsx | 15 ++++++++ .../mastodon/components/hashtag_bar.tsx | 34 +++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx b/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx index c7db485d0..1856b7109 100644 --- a/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/__tests__/hashtag_bar.tsx @@ -105,6 +105,21 @@ describe('computeHashtagBarForStatus', () => { ); }); + it('handles server-side normalized tags with accentuated characters', () => { + const status = createStatus( + '

Text

#éaa #Éaa

', + ['eaa'], // The server may normalize the hashtags in the `tags` attribute + ); + + const { hashtagsInBar, statusContentProps } = + computeHashtagBarForStatus(status); + + expect(hashtagsInBar).toEqual(['Éaa']); + expect(statusContentProps.statusContent).toMatchInlineSnapshot( + `"

Text

"`, + ); + }); + it('does not display in bar a hashtag in content with a case difference', () => { const status = createStatus( '

Text #Éaa

#éaa

', diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 8781c2663..75bd74da0 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -23,8 +23,9 @@ export type StatusLike = Record<{ }>; function normalizeHashtag(hashtag: string) { - if (hashtag && hashtag.startsWith('#')) return hashtag.slice(1); - else return hashtag; + return ( + hashtag && hashtag.startsWith('#') ? hashtag.slice(1) : hashtag + ).normalize('NFKC'); } function isNodeLinkHashtag(element: Node): element is HTMLLinkElement { @@ -70,9 +71,16 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) { } // Create the collator once, this is much more efficient -const collator = new Intl.Collator(undefined, { sensitivity: 'accent' }); +const collator = new Intl.Collator(undefined, { + sensitivity: 'base', // we use this to emulate the ASCII folding done on the server-side, hopefuly more efficiently +}); + function localeAwareInclude(collection: string[], value: string) { - return collection.find((item) => collator.compare(item, value) === 0); + const normalizedValue = value.normalize('NFKC'); + + return !!collection.find( + (item) => collator.compare(item.normalize('NFKC'), normalizedValue) === 0, + ); } // We use an intermediate function here to make it easier to test @@ -121,11 +129,13 @@ export function computeHashtagBarForStatus(status: StatusLike): { // try to see if the last line is only hashtags let onlyHashtags = true; + const normalizedTagNames = tagNames.map((tag) => tag.normalize('NFKC')); + Array.from(lastChild.childNodes).forEach((node) => { if (isNodeLinkHashtag(node) && node.textContent) { const normalized = normalizeHashtag(node.textContent); - if (!localeAwareInclude(tagNames, normalized)) { + if (!localeAwareInclude(normalizedTagNames, normalized)) { // stop here, this is not a real hashtag, so consider it as text onlyHashtags = false; return; @@ -140,12 +150,14 @@ export function computeHashtagBarForStatus(status: StatusLike): { } }); - const hashtagsInBar = tagNames.filter( - (tag) => - // the tag does not appear at all in the status content, it is an out-of-band tag - !localeAwareInclude(contentHashtags, tag) && - !localeAwareInclude(lastLineHashtags, tag), - ); + const hashtagsInBar = tagNames.filter((tag) => { + const normalizedTag = tag.normalize('NFKC'); + // the tag does not appear at all in the status content, it is an out-of-band tag + return ( + !localeAwareInclude(contentHashtags, normalizedTag) && + !localeAwareInclude(lastLineHashtags, normalizedTag) + ); + }); const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0; const hasMedia = status.get('media_attachments').size > 0; From 74b8b8ea144e5515684c76d96e62c778a3bf6971 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 08:22:48 +0200 Subject: [PATCH 24/52] Update dependency rails to v7.0.7.2 (#26612) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 706df8ffd..64ad456f1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -39,47 +39,47 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.7) - actionpack (= 7.0.7) - activesupport (= 7.0.7) + actioncable (7.0.7.2) + actionpack (= 7.0.7.2) + activesupport (= 7.0.7.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.7) - actionpack (= 7.0.7) - activejob (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + actionmailbox (7.0.7.2) + actionpack (= 7.0.7.2) + activejob (= 7.0.7.2) + activerecord (= 7.0.7.2) + activestorage (= 7.0.7.2) + activesupport (= 7.0.7.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.7) - actionpack (= 7.0.7) - actionview (= 7.0.7) - activejob (= 7.0.7) - activesupport (= 7.0.7) + actionmailer (7.0.7.2) + actionpack (= 7.0.7.2) + actionview (= 7.0.7.2) + activejob (= 7.0.7.2) + activesupport (= 7.0.7.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.7) - actionview (= 7.0.7) - activesupport (= 7.0.7) + actionpack (7.0.7.2) + actionview (= 7.0.7.2) + activesupport (= 7.0.7.2) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.7) - actionpack (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + actiontext (7.0.7.2) + actionpack (= 7.0.7.2) + activerecord (= 7.0.7.2) + activestorage (= 7.0.7.2) + activesupport (= 7.0.7.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.7) - activesupport (= 7.0.7) + actionview (7.0.7.2) + activesupport (= 7.0.7.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -89,22 +89,22 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.0.7) - activesupport (= 7.0.7) + activejob (7.0.7.2) + activesupport (= 7.0.7.2) globalid (>= 0.3.6) - activemodel (7.0.7) - activesupport (= 7.0.7) - activerecord (7.0.7) - activemodel (= 7.0.7) - activesupport (= 7.0.7) - activestorage (7.0.7) - actionpack (= 7.0.7) - activejob (= 7.0.7) - activerecord (= 7.0.7) - activesupport (= 7.0.7) + activemodel (7.0.7.2) + activesupport (= 7.0.7.2) + activerecord (7.0.7.2) + activemodel (= 7.0.7.2) + activesupport (= 7.0.7.2) + activestorage (7.0.7.2) + actionpack (= 7.0.7.2) + activejob (= 7.0.7.2) + activerecord (= 7.0.7.2) + activesupport (= 7.0.7.2) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.7) + activesupport (7.0.7.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -556,20 +556,20 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (7.0.7) - actioncable (= 7.0.7) - actionmailbox (= 7.0.7) - actionmailer (= 7.0.7) - actionpack (= 7.0.7) - actiontext (= 7.0.7) - actionview (= 7.0.7) - activejob (= 7.0.7) - activemodel (= 7.0.7) - activerecord (= 7.0.7) - activestorage (= 7.0.7) - activesupport (= 7.0.7) + rails (7.0.7.2) + actioncable (= 7.0.7.2) + actionmailbox (= 7.0.7.2) + actionmailer (= 7.0.7.2) + actionpack (= 7.0.7.2) + actiontext (= 7.0.7.2) + actionview (= 7.0.7.2) + activejob (= 7.0.7.2) + activemodel (= 7.0.7.2) + activerecord (= 7.0.7.2) + activestorage (= 7.0.7.2) + activesupport (= 7.0.7.2) bundler (>= 1.15.0) - railties (= 7.0.7) + railties (= 7.0.7.2) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -584,9 +584,9 @@ GEM rails-i18n (7.0.7) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.0.7) - actionpack (= 7.0.7) - activesupport (= 7.0.7) + railties (7.0.7.2) + actionpack (= 7.0.7.2) + activesupport (= 7.0.7.2) method_source rake (>= 12.2) thor (~> 1.0) From ea1a221e2de95bf40aefaa34cab8b08716f941a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 08:26:30 +0200 Subject: [PATCH 25/52] Update dependency react-textarea-autosize to v8.5.3 (#26607) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 779b3a21b..a57b203de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10086,9 +10086,9 @@ react-test-renderer@^18.2.0: scheduler "^0.23.0" react-textarea-autosize@*, react-textarea-autosize@^8.4.1: - version "8.5.2" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.2.tgz#6421df2b5b50b9ca8c5e96fd31be688ea7fa2f9d" - integrity sha512-uOkyjkEl0ByEK21eCJMHDGBAAd/BoFQBawYK5XItjAmCTeSbjxghd8qnt7nzsLYzidjnoObu6M26xts0YGKsGg== + version "8.5.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== dependencies: "@babel/runtime" "^7.20.13" use-composed-ref "^1.3.0" From cf6f70799b128d385d830d1f55eae8046bea3c60 Mon Sep 17 00:00:00 2001 From: Robert R George Date: Tue, 22 Aug 2023 23:27:24 -0700 Subject: [PATCH 26/52] Add support for federating `memorial` attribute (#26583) --- app/helpers/context_helper.rb | 1 + app/serializers/activitypub/actor_serializer.rb | 5 +++-- app/services/activitypub/process_account_service.rb | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index d8fa9b92e..945ef9b91 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -21,6 +21,7 @@ module ContextHelper blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' }, discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' }, indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' }, + memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' }, voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' }, olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 36397857f..4998d0039 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -7,13 +7,14 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer context :security context_extensions :manually_approves_followers, :featured, :also_known_as, - :moved_to, :property_value, :discoverable, :olm, :suspended + :moved_to, :property_value, :discoverable, :olm, :suspended, + :memorial attributes :id, :type, :following, :followers, :inbox, :outbox, :featured, :featured_tags, :preferred_username, :name, :summary, :url, :manually_approves_followers, - :discoverable, :published + :discoverable, :published, :memorial has_one :public_key, serializer: ActivityPub::PublicKeySerializer diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index ae30e88a2..1304ca824 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -116,6 +116,7 @@ class ActivityPub::ProcessAccountService < BaseService @account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) } @account.discoverable = @json['discoverable'] || false @account.indexable = @json['indexable'] || false + @account.memorial = @json['memorial'] || false end def set_fetchable_key! From 3aac12981c6db519e6fcbee5509b2a146c3f4d1e Mon Sep 17 00:00:00 2001 From: yufushiro <62991447+yufushiro@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:44:56 +0900 Subject: [PATCH 27/52] Fix unexpected audio stream transcoding when uploaded video is eligible to passthrough (#26608) Co-authored-by: Claire --- lib/paperclip/transcoder.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/paperclip/transcoder.rb b/lib/paperclip/transcoder.rb index be40b4924..0f2e30f7d 100644 --- a/lib/paperclip/transcoder.rb +++ b/lib/paperclip/transcoder.rb @@ -37,12 +37,14 @@ module Paperclip @output_options['f'] = 'image2' @output_options['vframes'] = 1 when 'mp4' - @output_options['acodec'] = 'aac' - @output_options['strict'] = 'experimental' - - if high_vfr?(metadata) && !eligible_to_passthrough?(metadata) - @output_options['vsync'] = 'vfr' - @output_options['r'] = @vfr_threshold + unless eligible_to_passthrough?(metadata) + @output_options['acodec'] = 'aac' + @output_options['strict'] = 'experimental' + + if high_vfr?(metadata) + @output_options['vsync'] = 'vfr' + @output_options['r'] = @vfr_threshold + end end end From 85057865b4e6ce8ec09feee84ceb6f931300548e Mon Sep 17 00:00:00 2001 From: jsgoldstein Date: Wed, 23 Aug 2023 09:40:09 -0400 Subject: [PATCH 28/52] Update Account Search to prioritize username over display name (#26623) --- app/services/account_search_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index a15b69121..b437ff475 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -124,7 +124,7 @@ class AccountSearchService < BaseService multi_match: { query: @query, type: 'bool_prefix', - fields: %w(username username.* display_name display_name.*), + fields: %w(username^2 username.*^2 display_name display_name.*), }, } end From 44ba785242c14bdfcd95854683a69a8451288dba Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 23 Aug 2023 15:40:31 +0200 Subject: [PATCH 29/52] Change the hashtag bar to be hidden when there is a CW and the post is not expanded (#26615) --- app/javascript/mastodon/components/status.jsx | 5 +++-- .../mastodon/features/status/components/detailed_status.jsx | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 45a2106db..30692d1cd 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -546,6 +546,7 @@ class Status extends ImmutablePureComponent { const visibilityIcon = visibilityIconInfo[status.get('visibility')]; const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); + const expanded = !status.get('hidden') return ( @@ -574,7 +575,7 @@ class Status extends ImmutablePureComponent {
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 401550e49..e41a042c7 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -293,6 +293,7 @@ class DetailedStatus extends ImmutablePureComponent { } const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); + const expanded = !status.get('hidden') return (
@@ -318,7 +319,7 @@ class DetailedStatus extends ImmutablePureComponent { {media} - {hashtagBar} + {expanded && hashtagBar}
From 152b10b6246987bfb2cc73ecd2a20578d05b62dc Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Wed, 23 Aug 2023 15:43:41 +0200 Subject: [PATCH 30/52] Fix some React warnings (#26609) --- app/javascript/mastodon/features/explore/results.jsx | 8 ++++---- app/javascript/mastodon/features/status/index.jsx | 2 +- .../mastodon/features/ui/components/modal_root.jsx | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/features/explore/results.jsx b/app/javascript/mastodon/features/explore/results.jsx index bf2f0b95a..7d1ce69ad 100644 --- a/app/javascript/mastodon/features/explore/results.jsx +++ b/app/javascript/mastodon/features/explore/results.jsx @@ -108,10 +108,10 @@ class Results extends PureComponent { return ( <>
- - - - + + + +
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index f1d591c73..799354d18 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -568,7 +568,7 @@ class Status extends ImmutablePureComponent { onMoveUp={this.handleMoveUp} onMoveDown={this.handleMoveDown} contextType='thread' - previousId={i > 0 && list.get(i - 1)} + previousId={i > 0 ? list.get(i - 1) : undefined} nextId={list.get(i + 1) || (ancestors && statusId)} rootId={statusId} /> diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index fb6acfaea..f6de5dad3 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -115,7 +115,10 @@ export default class ModalRoot extends PureComponent { {visible && ( <> - {(SpecificComponent) => } + {(SpecificComponent) => { + const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined; + return + }} From 613cfd625c8be11b4fb91d769ddbeee7a535a57a Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 23 Aug 2023 15:44:52 +0200 Subject: [PATCH 31/52] Change hashtag bar tags to be de-emphasized (#26606) --- .../mastodon/components/hashtag_bar.tsx | 2 +- app/javascript/styles/mastodon/components.scss | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 75bd74da0..674c481b8 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -216,7 +216,7 @@ const HashtagBar: React.FC<{
{revealedHashtags.map((hashtag) => ( - #{hashtag} + #{hashtag} ))} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 7662f3d9b..664f6ca02 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -9305,16 +9305,15 @@ noscript { a { display: inline-flex; - border-radius: 4px; - background: rgba($highlight-text-color, 0.2); - color: $highlight-text-color; - padding: 0.4em 0.6em; + color: $dark-text-color; text-decoration: none; - &:hover, - &:focus, - &:active { - background: rgba($highlight-text-color, 0.3); + &:hover { + text-decoration: none; + + span { + text-decoration: underline; + } } } } From de8c2427a5d93e2e1d03681d0c9bae3a367dfa2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:45:21 +0200 Subject: [PATCH 32/52] Update dependency immutable to v4.3.3 (#26622) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a57b203de..7ef2a0d5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6643,9 +6643,9 @@ immutable@^3.8.2: integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== immutable@^4.0.0, immutable@^4.0.0-rc.1, immutable@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.2.tgz#f89d910f8dfb6e15c03b2cae2faaf8c1f66455fe" - integrity sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA== + version "4.3.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.3.tgz#8934ff6826d996a7642c8dc4b46e694dd19561e3" + integrity sha512-808ZFYMsIRAjLAu5xkKo0TsbY9LBy9H5MazTKIEHerNkg0ymgilGfBPMR/3G7d/ihGmuK2Hw8S1izY2d3kd3wA== import-fresh@^3.2.1: version "3.3.0" From 060b554a9d13465366c45da0a5c4c841472e872c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:45:38 +0200 Subject: [PATCH 33/52] Update dependency oj to v3.16.0 (#26520) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 64ad456f1..e3d9eac96 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -482,7 +482,7 @@ GEM nokogiri (1.15.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) - oj (3.15.0) + oj (3.16.0) omniauth (2.1.1) hashie (>= 3.4.6) rack (>= 2.2.3) From 34f5b90dc77956d33750695124f23c37a6ea7fd5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:45:56 +0200 Subject: [PATCH 34/52] Update dependency sass to v1.66.1 (#26534) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7ef2a0d5b..403748ea9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10599,9 +10599,9 @@ sass-loader@^10.2.0: semver "^7.3.2" sass@^1.62.1: - version "1.65.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.65.1.tgz#8f283b0c26335a88246a448d22e1342ba2ea1432" - integrity sha512-9DINwtHmA41SEd36eVPQ9BJKpn7eKDQmUHmpI0y5Zv2Rcorrh0zS+cFrt050hdNbmmCNKTW3hV5mWfuegNRsEA== + version "1.66.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.66.1.tgz#04b51c4671e4650aa393740e66a4e58b44d055b1" + integrity sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" From b91724fb9d0839365391310e20c2589ff6062d4f Mon Sep 17 00:00:00 2001 From: jsgoldstein Date: Wed, 23 Aug 2023 09:46:14 -0400 Subject: [PATCH 35/52] Add elastic search installation into Vagrantfile (#26512) --- .env.vagrant | 4 ++++ Vagrantfile | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.env.vagrant b/.env.vagrant index 32ed9b922..69c1bf1fb 100644 --- a/.env.vagrant +++ b/.env.vagrant @@ -2,3 +2,7 @@ VAGRANT=true LOCAL_DOMAIN=mastodon.local BIND=0.0.0.0 DB_HOST=/var/run/postgresql/ + +ES_ENABLED=true +ES_HOST=localhost +ES_PORT=9200 \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile index 880cc1849..1117d62ff 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -60,6 +60,37 @@ sudo usermod -a -G rvm $USER SCRIPT +$provisionElasticsearch = <