Merge remote-tracking branch 'parent/main' into kbtopic-remove-quote
This commit is contained in:
commit
7c65b6f9df
464 changed files with 8217 additions and 8135 deletions
|
@ -9,22 +9,20 @@ class Mastodon::RedisConfiguration
|
|||
|
||||
def base
|
||||
@base ||= setup_config(prefix: nil, defaults: DEFAULTS)
|
||||
.merge(namespace: base_namespace)
|
||||
end
|
||||
|
||||
def sidekiq
|
||||
@sidekiq ||= setup_config(prefix: 'SIDEKIQ_')
|
||||
.merge(namespace: sidekiq_namespace)
|
||||
end
|
||||
|
||||
def cache
|
||||
@cache ||= setup_config(prefix: 'CACHE_')
|
||||
.merge({
|
||||
namespace: cache_namespace,
|
||||
namespace: 'cache',
|
||||
expires_in: 10.minutes,
|
||||
connect_timeout: 5,
|
||||
pool: {
|
||||
size: Sidekiq.server? ? Sidekiq[:concurrency] : Integer(ENV['MAX_THREADS'] || 5),
|
||||
size: Sidekiq.server? ? Sidekiq.default_configuration[:concurrency] : Integer(ENV['MAX_THREADS'] || 5),
|
||||
timeout: 5,
|
||||
},
|
||||
})
|
||||
|
@ -36,24 +34,6 @@ class Mastodon::RedisConfiguration
|
|||
ENV['REDIS_DRIVER'] == 'ruby' ? :ruby : :hiredis
|
||||
end
|
||||
|
||||
def namespace
|
||||
@namespace ||= ENV.fetch('REDIS_NAMESPACE', nil)
|
||||
end
|
||||
|
||||
def base_namespace
|
||||
return "mastodon_test#{ENV.fetch('TEST_ENV_NUMBER', nil)}" if Rails.env.test?
|
||||
|
||||
namespace
|
||||
end
|
||||
|
||||
def sidekiq_namespace
|
||||
namespace
|
||||
end
|
||||
|
||||
def cache_namespace
|
||||
namespace ? "#{namespace}_cache" : 'cache'
|
||||
end
|
||||
|
||||
def setup_config(prefix: nil, defaults: {})
|
||||
prefix = "#{prefix}REDIS_"
|
||||
|
||||
|
@ -62,7 +42,7 @@ class Mastodon::RedisConfiguration
|
|||
password = ENV.fetch("#{prefix}PASSWORD", nil)
|
||||
host = ENV.fetch("#{prefix}HOST", defaults[:host])
|
||||
port = ENV.fetch("#{prefix}PORT", defaults[:port])
|
||||
db = ENV.fetch("#{prefix}DB", defaults[:db])
|
||||
db = Rails.env.test? ? ENV.fetch('TEST_ENV_NUMBER', defaults[:db]).to_i + 1 : ENV.fetch("#{prefix}DB", defaults[:db])
|
||||
|
||||
return { url:, driver: } if url
|
||||
|
||||
|
|
|
@ -2,21 +2,28 @@
|
|||
|
||||
module PremailerBundledAssetStrategy
|
||||
def load(url)
|
||||
asset_host = ENV['CDN_HOST'] || ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil)
|
||||
if ViteRuby.instance.dev_server_running?
|
||||
# Request from the dev server
|
||||
return unless url.start_with?("/#{ViteRuby.config.public_output_dir}/")
|
||||
|
||||
if Webpacker.dev_server.running?
|
||||
asset_host = "#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}"
|
||||
url = File.join(asset_host, url)
|
||||
headers = {}
|
||||
# Vite dev server wants this header for CSS files, otherwise it will respond with a JS file that inserts the CSS (to support hot reloading)
|
||||
headers['Accept'] = 'text/css' if url.end_with?('.scss', '.css')
|
||||
|
||||
Net::HTTP.get(
|
||||
URI("#{ViteRuby.config.origin}#{url}"),
|
||||
headers
|
||||
).presence
|
||||
else
|
||||
url = url.delete_prefix(Rails.configuration.action_controller.asset_host) if Rails.configuration.action_controller.asset_host.present?
|
||||
url = url.delete_prefix('/')
|
||||
path = Rails.public_path.join(url)
|
||||
return unless path.exist?
|
||||
|
||||
path.read
|
||||
end
|
||||
|
||||
css = if url.start_with?('http')
|
||||
HTTP.get(url).to_s
|
||||
else
|
||||
url = url[1..] if url.start_with?('/')
|
||||
Rails.public_path.join(url).read
|
||||
end
|
||||
|
||||
css.gsub(%r{url\(/}, "url(#{asset_host}/")
|
||||
rescue ViteRuby::MissingEntrypointError
|
||||
# If the path is not in the manifest, ignore it
|
||||
end
|
||||
|
||||
module_function :load
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Redis
|
||||
module NamespaceExtensions
|
||||
def exists?(...)
|
||||
call_with_namespace('exists?', ...)
|
||||
end
|
||||
|
||||
def with
|
||||
yield self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Redis::Namespace::COMMANDS['exists?'] = [:first]
|
||||
Redis::Namespace.prepend(Redis::NamespaceExtensions)
|
|
@ -31,6 +31,7 @@ class Sanitize
|
|||
next true if /^(h|p|u|dt|e)-/.match?(e) # microformats classes
|
||||
next true if /^(mention|hashtag)$/.match?(e) # semantic classes
|
||||
next true if /^(ellipsis|invisible)$/.match?(e) # link formatting classes
|
||||
next true if e == 'quote-inline'
|
||||
end
|
||||
|
||||
node['class'] = class_list.join(' ')
|
||||
|
@ -133,6 +134,7 @@ class Sanitize
|
|||
'span' => %w(class translate),
|
||||
'ol' => %w(start reversed),
|
||||
'li' => %w(value),
|
||||
'p' => %w(class),
|
||||
},
|
||||
|
||||
add_attributes: {
|
||||
|
|
|
@ -14,7 +14,9 @@ end
|
|||
|
||||
if Rake::Task.task_defined?('assets:precompile')
|
||||
Rake::Task['assets:precompile'].enhance do
|
||||
Webpacker.manifest.refresh
|
||||
Rake::Task['assets:generate_static_pages'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
# We don't want vite_ruby to run yarn, we do that in a separate step
|
||||
Rake::Task['vite:install_dependencies'].clear
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace :db do
|
|||
|
||||
task pre_migration_check: :environment do
|
||||
pg_version = ActiveRecord::Base.connection.database_version
|
||||
abort 'This version of Mastodon requires PostgreSQL 12.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 120_000
|
||||
abort 'This version of Mastodon requires PostgreSQL 13.0 or newer. Please update PostgreSQL before updating Mastodon.' if pg_version < 130_000
|
||||
|
||||
schema_version = ActiveRecord::Migrator.current_version
|
||||
abort <<~MESSAGE if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && schema_version < 2023_09_07_150100
|
||||
|
|
422
lib/tasks/dev.rake
Normal file
422
lib/tasks/dev.rake
Normal file
|
@ -0,0 +1,422 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :dev do
|
||||
desc 'Populate database with test data. Can be run multiple times. Should not be run in production environments'
|
||||
task populate_sample_data: :environment do
|
||||
# Create a valid account to showcase multiple post types
|
||||
showcase_account = Account.create_with(username: 'showcase_account').find_or_create_by!(id: 10_000_000)
|
||||
showcase_user = User.create_with(
|
||||
account_id: showcase_account.id,
|
||||
agreement: true,
|
||||
password: SecureRandom.hex,
|
||||
email: ENV.fetch('TEST_DATA_SHOWCASE_EMAIL', 'showcase_account@joinmastodon.org'),
|
||||
confirmed_at: Time.now.utc,
|
||||
approved: true,
|
||||
bypass_registration_checks: true
|
||||
).find_or_create_by!(id: 10_000_000)
|
||||
showcase_user.mark_email_as_confirmed!
|
||||
showcase_user.approve!
|
||||
|
||||
french_post = Status.create_with(
|
||||
text: 'Ceci est un sondage public écrit en Français',
|
||||
language: 'fr',
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
poll_attributes: {
|
||||
voters_count: 0,
|
||||
account: showcase_account,
|
||||
expires_at: 1.day.from_now,
|
||||
options: ['ceci est un choix', 'ceci est un autre choix'],
|
||||
multiple: false,
|
||||
}
|
||||
).find_or_create_by!(id: 10_000_000)
|
||||
|
||||
private_mentionless = Status.create_with(
|
||||
text: 'This is a private message written in English',
|
||||
language: 'en',
|
||||
account: showcase_account,
|
||||
visibility: :private
|
||||
).find_or_create_by!(id: 10_000_001)
|
||||
|
||||
public_self_reply_with_cw = Status.create_with(
|
||||
text: 'This is a public self-reply written in English; it has a CW and a multi-choice poll',
|
||||
spoiler_text: 'poll (CW example)',
|
||||
language: 'en',
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
thread: french_post,
|
||||
poll_attributes: {
|
||||
voters_count: 0,
|
||||
account: showcase_account,
|
||||
expires_at: 1.day.from_now,
|
||||
options: ['this is a choice', 'this is another choice', 'you can chose any number of them'],
|
||||
multiple: true,
|
||||
}
|
||||
).find_or_create_by!(id: 10_000_002)
|
||||
ProcessHashtagsService.new.call(public_self_reply_with_cw)
|
||||
|
||||
unlisted_self_reply_with_cw_tag_mention = Status.create_with(
|
||||
text: 'This is an unlisted (Quiet Public) self-reply written in #English; it has a CW, mentions @showcase_account, and uses an emoji 🦣',
|
||||
spoiler_text: 'CW example',
|
||||
language: 'en',
|
||||
account: showcase_account,
|
||||
visibility: :unlisted,
|
||||
thread: public_self_reply_with_cw
|
||||
).find_or_create_by!(id: 10_000_003)
|
||||
Mention.find_or_create_by!(status: unlisted_self_reply_with_cw_tag_mention, account: showcase_account)
|
||||
ProcessHashtagsService.new.call(unlisted_self_reply_with_cw_tag_mention)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/600x400.png'),
|
||||
description: 'Mastodon logo'
|
||||
).find_or_create_by!(id: 10_000_000)
|
||||
status_with_media = Status.create_with(
|
||||
text: "This is a public status with a picture and tags. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test",
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_004)
|
||||
media_attachment.update(status_id: status_with_media.id)
|
||||
ProcessHashtagsService.new.call(status_with_media)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/600x400.png'),
|
||||
description: 'Mastodon logo'
|
||||
).find_or_create_by!(id: 10_000_001)
|
||||
status_with_sensitive_media = Status.create_with(
|
||||
text: "This is the same public status with a picture and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test",
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
sensitive: true,
|
||||
thread: status_with_media
|
||||
).find_or_create_by!(id: 10_000_005)
|
||||
media_attachment.update(status_id: status_with_sensitive_media.id)
|
||||
ProcessHashtagsService.new.call(status_with_sensitive_media)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/600x400.png'),
|
||||
description: 'Mastodon logo'
|
||||
).find_or_create_by!(id: 10_000_002)
|
||||
status_with_cw_media = Status.create_with(
|
||||
text: "This is the same public status with a picture and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test",
|
||||
spoiler_text: 'Mastodon logo',
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
sensitive: true,
|
||||
thread: status_with_sensitive_media
|
||||
).find_or_create_by!(id: 10_000_006)
|
||||
media_attachment.update(status_id: status_with_cw_media.id)
|
||||
ProcessHashtagsService.new.call(status_with_cw_media)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/boop.ogg'),
|
||||
description: 'Mastodon boop'
|
||||
).find_or_create_by!(id: 10_000_003)
|
||||
status_with_audio = Status.create_with(
|
||||
text: "This is the same public status with an audio file and tags. The attached picture has an alt text\n\n#Mastodon #English #Test",
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
thread: status_with_cw_media
|
||||
).find_or_create_by!(id: 10_000_007)
|
||||
media_attachment.update(status_id: status_with_audio.id)
|
||||
ProcessHashtagsService.new.call(status_with_audio)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/boop.ogg'),
|
||||
description: 'Mastodon boop'
|
||||
).find_or_create_by!(id: 10_000_004)
|
||||
status_with_sensitive_audio = Status.create_with(
|
||||
text: "This is the same public status with an audio file and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #English #Test",
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
sensitive: true,
|
||||
thread: status_with_audio
|
||||
).find_or_create_by!(id: 10_000_008)
|
||||
media_attachment.update(status_id: status_with_sensitive_audio.id)
|
||||
ProcessHashtagsService.new.call(status_with_sensitive_audio)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/boop.ogg'),
|
||||
description: 'Mastodon boop'
|
||||
).find_or_create_by!(id: 10_000_005)
|
||||
status_with_cw_audio = Status.create_with(
|
||||
text: "This is the same public status with an audio file and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #English #Test",
|
||||
spoiler_text: 'Mastodon boop',
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
sensitive: true,
|
||||
thread: status_with_sensitive_audio
|
||||
).find_or_create_by!(id: 10_000_009)
|
||||
media_attachment.update(status_id: status_with_cw_audio.id)
|
||||
ProcessHashtagsService.new.call(status_with_cw_audio)
|
||||
|
||||
media_attachments = [
|
||||
MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/600x400.png'),
|
||||
description: 'Mastodon logo'
|
||||
).find_or_create_by!(id: 10_000_006),
|
||||
MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/attachment.jpg')
|
||||
).find_or_create_by!(id: 10_000_007),
|
||||
MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/avatar-high.gif'),
|
||||
description: 'Walking cartoon cat'
|
||||
).find_or_create_by!(id: 10_000_008),
|
||||
MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/text.png'),
|
||||
description: 'Text saying “Hello Mastodon”'
|
||||
).find_or_create_by!(id: 10_000_009),
|
||||
]
|
||||
status_with_multiple_attachments = Status.create_with(
|
||||
text: "This is a post with multiple attachments, not all of which have a description\n\n#Mastodon #English #Test",
|
||||
spoiler_text: 'multiple attachments',
|
||||
ordered_media_attachment_ids: media_attachments.pluck(:id),
|
||||
account: showcase_account,
|
||||
visibility: :public,
|
||||
sensitive: true,
|
||||
thread: status_with_cw_audio
|
||||
).find_or_create_by!(id: 10_000_010)
|
||||
media_attachments.each { |attachment| attachment.update!(status_id: status_with_multiple_attachments.id) }
|
||||
ProcessHashtagsService.new.call(status_with_multiple_attachments)
|
||||
|
||||
remote_account = Account.create_with(
|
||||
username: 'fake.example',
|
||||
domain: 'example.org',
|
||||
uri: 'https://example.org/foo/bar',
|
||||
url: 'https://example.org/foo/bar',
|
||||
locked: true
|
||||
).find_or_create_by!(id: 10_000_001)
|
||||
|
||||
remote_formatted_post = Status.create_with(
|
||||
text: <<~HTML,
|
||||
<p>This is a post with a variety of HTML in it</p>
|
||||
<p>For instance, <strong>this text is bold</strong> and <b>this one as well</b>, while <del>this text is stricken through</del> and <s>this one as well</s>.</p>
|
||||
<blockquote>
|
||||
<p>This thing, here, is a block quote<br/>with some <strong>bold</strong> as well</p>
|
||||
<ul>
|
||||
<li>a list item</li>
|
||||
<li>
|
||||
and another with
|
||||
<ul>
|
||||
<li>nested</li>
|
||||
<li>items!</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<pre><code>// And this is some code
|
||||
// with two lines of comments
|
||||
</code></pre>
|
||||
<p>And this is <code>inline</code> code</p>
|
||||
<p>Finally, please observe this Ruby element: <ruby> 明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp> </ruby></p>
|
||||
HTML
|
||||
account: remote_account,
|
||||
uri: 'https://example.org/foo/bar/baz',
|
||||
url: 'https://example.org/foo/bar/baz'
|
||||
).find_or_create_by!(id: 10_000_011)
|
||||
Status.create_with(account: showcase_account, reblog: remote_formatted_post).find_or_create_by!(id: 10_000_012)
|
||||
|
||||
unattached_quote_post = Status.create_with(
|
||||
text: 'This is a quote of a post that does not exist',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_013)
|
||||
Quote.create_with(
|
||||
status: unattached_quote_post,
|
||||
quoted_status: nil
|
||||
).find_or_create_by!(id: 10_000_000)
|
||||
|
||||
self_quote = Status.create_with(
|
||||
text: 'This is a quote of a public self-post',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_014)
|
||||
Quote.create_with(
|
||||
status: self_quote,
|
||||
quoted_status: status_with_media,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_001)
|
||||
|
||||
nested_self_quote = Status.create_with(
|
||||
text: 'This is a quote of a public self-post which itself is a self-quote',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_015)
|
||||
Quote.create_with(
|
||||
status: nested_self_quote,
|
||||
quoted_status: self_quote,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_002)
|
||||
|
||||
recursive_self_quote = Status.create_with(
|
||||
text: 'This is a recursive self-quote; no real reason for it to exist, but just to make sure we handle them gracefuly',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_016)
|
||||
Quote.create_with(
|
||||
status: recursive_self_quote,
|
||||
quoted_status: recursive_self_quote,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_003)
|
||||
|
||||
self_private_quote = Status.create_with(
|
||||
text: 'This is a public post of a private self-post: the quoted post should not be visible to non-followers',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_017)
|
||||
Quote.create_with(
|
||||
status: self_private_quote,
|
||||
quoted_status: private_mentionless,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_004)
|
||||
|
||||
uncwed_quote_cwed = Status.create_with(
|
||||
text: 'This is a quote without CW of a quoted post that has a CW',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_018)
|
||||
Quote.create_with(
|
||||
status: uncwed_quote_cwed,
|
||||
quoted_status: public_self_reply_with_cw,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_005)
|
||||
|
||||
cwed_quote_cwed = Status.create_with(
|
||||
text: 'This is a quote with a CW of a quoted post that itself has a CW',
|
||||
spoiler_text: 'Quote post with a CW',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_019)
|
||||
Quote.create_with(
|
||||
status: cwed_quote_cwed,
|
||||
quoted_status: public_self_reply_with_cw,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_006)
|
||||
|
||||
pending_quote_post = Status.create_with(
|
||||
text: 'This quote post is pending',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_020)
|
||||
Quote.create_with(
|
||||
status: pending_quote_post,
|
||||
quoted_status: remote_formatted_post,
|
||||
activity_uri: 'https://foo/bar',
|
||||
state: :pending
|
||||
).find_or_create_by!(id: 10_000_007)
|
||||
|
||||
rejected_quote_post = Status.create_with(
|
||||
text: 'This quote post is rejected',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_021)
|
||||
Quote.create_with(
|
||||
status: rejected_quote_post,
|
||||
quoted_status: remote_formatted_post,
|
||||
activity_uri: 'https://foo/foo',
|
||||
state: :rejected
|
||||
).find_or_create_by!(id: 10_000_008)
|
||||
|
||||
revoked_quote_post = Status.create_with(
|
||||
text: 'This quote post is revoked',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_022)
|
||||
Quote.create_with(
|
||||
status: revoked_quote_post,
|
||||
quoted_status: remote_formatted_post,
|
||||
activity_uri: 'https://foo/baz',
|
||||
state: :revoked
|
||||
).find_or_create_by!(id: 10_000_009)
|
||||
|
||||
StatusPin.create_with(account: showcase_account, status: public_self_reply_with_cw).find_or_create_by!(id: 10_000_000)
|
||||
StatusPin.create_with(account: showcase_account, status: private_mentionless).find_or_create_by!(id: 10_000_001)
|
||||
|
||||
showcase_account.update!(
|
||||
display_name: 'Mastodon test/showcase account',
|
||||
note: 'Test account to showcase many Mastodon features. Most of its posts are public, but some are private!'
|
||||
)
|
||||
|
||||
remote_quote = Status.create_with(
|
||||
text: <<~HTML,
|
||||
<p>This is a self-quote of a remote formatted post</p>
|
||||
<p class="quote-inline">RE: <a href="https://example.org/foo/bar/baz">https://example.org/foo/bar/baz</a></p>
|
||||
HTML
|
||||
account: remote_account,
|
||||
uri: 'https://example.org/foo/bar/quote',
|
||||
url: 'https://example.org/foo/bar/quote'
|
||||
).find_or_create_by!(id: 10_000_023)
|
||||
Quote.create_with(
|
||||
status: remote_quote,
|
||||
quoted_status: remote_formatted_post,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_010)
|
||||
Status.create_with(
|
||||
account: showcase_account,
|
||||
reblog: remote_quote
|
||||
).find_or_create_by!(id: 10_000_024)
|
||||
|
||||
media_attachment = MediaAttachment.create_with(
|
||||
account: showcase_account,
|
||||
file: File.open('spec/fixtures/files/attachment.jpg')
|
||||
).find_or_create_by!(id: 10_000_010)
|
||||
quote_post_with_media = Status.create_with(
|
||||
text: "This is a status with a picture and tags which also quotes a status with a picture.\n\n#Mastodon #Test",
|
||||
ordered_media_attachment_ids: [media_attachment.id],
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_025)
|
||||
media_attachment.update(status_id: quote_post_with_media.id)
|
||||
ProcessHashtagsService.new.call(quote_post_with_media)
|
||||
Quote.create_with(
|
||||
status: quote_post_with_media,
|
||||
quoted_status: status_with_media,
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_011)
|
||||
|
||||
showcase_sidekick_account = Account.create_with(username: 'showcase_sidekick').find_or_create_by!(id: 10_000_002)
|
||||
sidekick_user = User.create_with(
|
||||
account_id: showcase_sidekick_account.id,
|
||||
agreement: true,
|
||||
password: SecureRandom.hex,
|
||||
email: ENV.fetch('TEST_DATA_SHOWCASE_SIDEKICK_EMAIL', 'showcase_sidekick@joinmastodon.org'),
|
||||
confirmed_at: Time.now.utc,
|
||||
approved: true,
|
||||
bypass_registration_checks: true
|
||||
).find_or_create_by!(id: 10_000_001)
|
||||
sidekick_user.mark_email_as_confirmed!
|
||||
sidekick_user.approve!
|
||||
|
||||
sidekick_post = Status.create_with(
|
||||
text: 'This post only exists to be quoted.',
|
||||
account: showcase_sidekick_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_026)
|
||||
sidekick_quote_post = Status.create_with(
|
||||
text: 'This is a quote of a different user.',
|
||||
account: showcase_account,
|
||||
visibility: :public
|
||||
).find_or_create_by!(id: 10_000_027)
|
||||
Quote.create_with(
|
||||
status: sidekick_quote_post,
|
||||
quoted_status: sidekick_post,
|
||||
activity_uri: 'https://foo/cross-account-quote',
|
||||
state: :accepted
|
||||
).find_or_create_by!(id: 10_000_012)
|
||||
end
|
||||
end
|
|
@ -32,7 +32,7 @@ namespace :mastodon do
|
|||
prompt.say('Single user mode disables registrations and redirects the landing page to your public profile.')
|
||||
env['SINGLE_USER_MODE'] = prompt.yes?('Do you want to enable single user mode?', default: false)
|
||||
|
||||
%w(SECRET_KEY_BASE OTP_SECRET).each do |key|
|
||||
%w(SECRET_KEY_BASE).each do |key|
|
||||
env[key] = SecureRandom.hex(64)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Disable this task as we use pnpm
|
||||
|
||||
require 'semantic_range'
|
||||
|
||||
Rake::Task['webpacker:check_yarn'].clear
|
||||
|
||||
namespace :webpacker do
|
||||
desc 'Verifies if Yarn is installed'
|
||||
task check_yarn: :environment do
|
||||
begin
|
||||
yarn_version = `yarn --version`.strip
|
||||
raise Errno::ENOENT if yarn_version.blank?
|
||||
|
||||
yarn_range = '>=4 <5'
|
||||
is_valid = begin
|
||||
SemanticRange.satisfies?(yarn_version, yarn_range)
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
unless is_valid
|
||||
warn "Mastodon and Webpacker requires Yarn \"#{yarn_range}\" and you are using #{yarn_version}"
|
||||
warn 'Exiting!'
|
||||
exit!
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
warn 'Yarn not installed. Please see the Mastodon documentation to install the correct version.'
|
||||
warn 'Exiting!'
|
||||
exit!
|
||||
end
|
||||
end
|
||||
end
|
132
lib/vite_ruby/sri_extensions.rb
Normal file
132
lib/vite_ruby/sri_extensions.rb
Normal file
|
@ -0,0 +1,132 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ViteRuby::ManifestIntegrityExtension
|
||||
def path_and_integrity_for(name, **)
|
||||
entry = lookup!(name, **)
|
||||
|
||||
{ path: entry.fetch('file'), integrity: entry.fetch('integrity', nil) }
|
||||
end
|
||||
|
||||
# Find a manifest entry by the *final* file name
|
||||
def integrity_hash_for_file(file_name)
|
||||
@integrity_cache ||= {}
|
||||
@integrity_cache[file_name] ||= begin
|
||||
entry = manifest.find { |_key, entry| entry['file'] == file_name }
|
||||
|
||||
entry[1].fetch('integrity', nil) if entry
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_entries_with_integrity(*names, **options)
|
||||
entries = names.map { |name| lookup!(name, **options) }
|
||||
script_paths = entries.map do |entry|
|
||||
{
|
||||
file: entry.fetch('file'),
|
||||
# TODO: Secure this so we require the integrity hash outside of dev
|
||||
integrity: entry['integrity'],
|
||||
}
|
||||
end
|
||||
|
||||
imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact
|
||||
|
||||
{
|
||||
scripts: script_paths,
|
||||
imports: imports.filter_map { |entry| { file: entry.fetch('file'), integrity: entry.fetch('integrity') } }.uniq,
|
||||
stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
|
||||
}
|
||||
end
|
||||
|
||||
# We need to override this method to not include the manifest, as in our case it is too large and will cause a JSON max nesting error rather than raising the expected exception
|
||||
def missing_entry_error(name, **)
|
||||
raise ViteRuby::MissingEntrypointError.new(
|
||||
file_name: resolve_entry_name(name, **),
|
||||
last_build: builder.last_build_metadata,
|
||||
manifest: '',
|
||||
config: config
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
ViteRuby::Manifest.prepend ViteRuby::ManifestIntegrityExtension
|
||||
|
||||
module ViteRails::TagHelpers::IntegrityExtension
|
||||
def vite_javascript_tag(*names,
|
||||
type: 'module',
|
||||
asset_type: :javascript,
|
||||
skip_preload_tags: false,
|
||||
skip_style_tags: false,
|
||||
crossorigin: 'anonymous',
|
||||
media: 'screen',
|
||||
**options)
|
||||
entries = vite_manifest.resolve_entries_with_integrity(*names, type: asset_type)
|
||||
|
||||
''.html_safe.tap do |tags|
|
||||
entries.fetch(:scripts).each do |script|
|
||||
tags << javascript_include_tag(
|
||||
script[:file],
|
||||
integrity: script[:integrity],
|
||||
crossorigin: crossorigin,
|
||||
type: type,
|
||||
extname: false,
|
||||
**options
|
||||
)
|
||||
end
|
||||
|
||||
unless skip_preload_tags
|
||||
entries.fetch(:imports).each do |import|
|
||||
tags << vite_preload_tag(import[:file], integrity: import[:integrity], crossorigin: crossorigin, **options)
|
||||
end
|
||||
end
|
||||
|
||||
options[:extname] = false if Rails::VERSION::MAJOR >= 7
|
||||
|
||||
unless skip_style_tags
|
||||
entries.fetch(:stylesheets).each do |stylesheet|
|
||||
# This is for stylesheets imported from Javascript. The entry for the JS entrypoint only contains the final CSS file name, so we need to look it up in the manifest
|
||||
tags << stylesheet_link_tag(
|
||||
stylesheet,
|
||||
integrity: vite_manifest.integrity_hash_for_file(stylesheet),
|
||||
media: media,
|
||||
**options
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def vite_stylesheet_tag(*names, **options)
|
||||
''.html_safe.tap do |tags|
|
||||
names.each do |name|
|
||||
entry = vite_manifest.path_and_integrity_for(name, type: :stylesheet)
|
||||
|
||||
options[:extname] = false if Rails::VERSION::MAJOR >= 7
|
||||
|
||||
tags << stylesheet_link_tag(entry[:path], integrity: entry[:integrity], **options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def vite_preload_file_tag(name,
|
||||
asset_type: :javascript,
|
||||
crossorigin: 'anonymous', **options)
|
||||
''.html_safe.tap do |tags|
|
||||
entries = vite_manifest.resolve_entries_with_integrity(name, type: asset_type)
|
||||
|
||||
entries.fetch(:scripts).each do |script|
|
||||
tags << vite_preload_tag(script[:file], integrity: script[:integrity], crossorigin: crossorigin, **options)
|
||||
end
|
||||
end
|
||||
rescue ViteRuby::MissingEntrypointError
|
||||
# Ignore this error, it is not critical if the file is not preloaded
|
||||
end
|
||||
|
||||
def vite_polyfills_tag(crossorigin: 'anonymous', **)
|
||||
return if ViteRuby.instance.dev_server_running?
|
||||
|
||||
entry = vite_manifest.path_and_integrity_for('polyfills', type: :virtual)
|
||||
|
||||
javascript_include_tag(entry[:path], type: 'module', integrity: entry[:integrity], crossorigin: crossorigin, **)
|
||||
end
|
||||
end
|
||||
|
||||
ViteRails::TagHelpers.prepend ViteRails::TagHelpers::IntegrityExtension
|
|
@ -1,27 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Webpacker::HelperExtensions
|
||||
def javascript_pack_tag(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :javascript, with_integrity: true)
|
||||
javascript_include_tag(src, options.merge(integrity: integrity))
|
||||
end
|
||||
|
||||
def stylesheet_pack_tag(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :stylesheet, with_integrity: true)
|
||||
stylesheet_link_tag(src, options.merge(integrity: integrity))
|
||||
end
|
||||
|
||||
def preload_pack_asset(name, **options)
|
||||
src, integrity = current_webpacker_instance.manifest.lookup!(name, with_integrity: true)
|
||||
|
||||
# This attribute will only work if the assets are on a different domain.
|
||||
# And Webpack will (correctly) only add it in this case, so we need to conditionally set it here
|
||||
# otherwise the preloaded request and the real request will have different crossorigin values
|
||||
# and the preloaded file wont be loaded
|
||||
crossorigin = 'anonymous' if Rails.configuration.action_controller.asset_host.present?
|
||||
|
||||
preload_link_tag(src, options.merge(integrity: integrity, crossorigin: crossorigin))
|
||||
end
|
||||
end
|
||||
|
||||
Webpacker::Helper.prepend(Webpacker::HelperExtensions)
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Webpacker::ManifestExtensions
|
||||
def lookup(name, pack_type = {})
|
||||
asset = super
|
||||
|
||||
if pack_type[:with_integrity] && asset.respond_to?(:dig)
|
||||
[asset['src'], asset['integrity']]
|
||||
elsif asset.respond_to?(:dig)
|
||||
asset['src']
|
||||
else
|
||||
asset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Webpacker::Manifest.prepend(Webpacker::ManifestExtensions)
|
Loading…
Add table
Add a link
Reference in a new issue