diff options
Diffstat (limited to 'config')
28 files changed, 540 insertions, 118 deletions
diff --git a/config/environments/production.rb b/config/environments/production.rb index 7fe381040..ce3c41799 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -115,11 +115,13 @@ Rails.application.configure do config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym config.action_dispatch.default_headers = { - 'Server' => 'Mastodon', - 'X-Frame-Options' => 'DENY', - 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '0', - 'Permissions-Policy' => 'interest-cohort=()', + 'Server' => 'Mastodon', + 'X-Frame-Options' => 'DENY', + 'X-Content-Type-Options' => 'nosniff', + 'X-XSS-Protection' => '0', + 'Permissions-Policy' => 'interest-cohort=()', + 'Referrer-Policy' => 'same-origin', + 'X-Clacks-Overhead' => 'GNU Natalie Nguyen' } config.x.otp_secret = ENV.fetch('OTP_SECRET') diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 7f879b1aa..a3d7baecd 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -59,6 +59,7 @@ ignore_unused: - 'errors.429' - 'admin.accounts.roles.*' - 'admin.action_logs.actions.*' + - 'themes.*' - 'statuses.attached.*' - 'move_handler.carry_{mutes,blocks}_over_text' - 'notification_mailer.*' diff --git a/config/initializers/0_duplicate_migrations.rb b/config/initializers/0_duplicate_migrations.rb new file mode 100644 index 000000000..6c45e4bd2 --- /dev/null +++ b/config/initializers/0_duplicate_migrations.rb @@ -0,0 +1,52 @@ +# Some migrations have been present in glitch-soc for a long time and have then +# been merged in upstream Mastodon, under a different version number. +# +# This puts us in an uneasy situation in which if we remove upstream's +# migration file, people migrating from upstream will end up having a conflict +# with their already-ran migration. +# +# On the other hand, if we keep upstream's migration and remove our own, +# any current glitch-soc user will have a conflict during migration. +# +# For lack of a better solution, as those migrations are indeed identical, +# we decided monkey-patching Rails' Migrator to completely ignore the duplicate, +# keeping only the one that has run, or an arbitrary one. + +ALLOWED_DUPLICATES = [20180410220657, 20180831171112].freeze + +module ActiveRecord + class Migrator + def self.new(direction, migrations, schema_migration, target_version = nil) + migrated = Set.new(Base.connection.migration_context.get_all_versions) + + migrations.group_by(&:name).each do |name, duplicates| + if duplicates.length > 1 && duplicates.all? { |m| ALLOWED_DUPLICATES.include?(m.version) } + # We have a set of allowed duplicates. Keep the migrated one, if any. + non_migrated = duplicates.reject { |m| migrated.include?(m.version.to_i) } + + if duplicates.length == non_migrated.length || non_migrated.length == 0 + # There weren't any migrated one, so we have to pick one “canonical” migration + migrations = migrations - duplicates[1..-1] + else + # Just reject every duplicate which hasn't been migrated yet + migrations = migrations - non_migrated + end + end + end + + super(direction, migrations, schema_migration, target_version) + end + end + + class MigrationContext + def needs_migration? + # A set of duplicated migrations is considered migrated if at least one of + # them is migrated. + migrated = get_all_versions + migrations.group_by(&:name).each do |name, duplicates| + return true unless duplicates.any? { |m| migrated.include?(m.version.to_i) } + end + return false + end + end +end diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index b377b7b4d..549ac3568 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -2,43 +2,45 @@ # For further information see the following documentation # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy -def host_to_url(str) - "http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}" unless str.blank? -end +if Rails.env.production? + assets_host = Rails.configuration.action_controller.asset_host || "https://#{ENV['WEB_DOMAIN'] || ENV['LOCAL_DOMAIN']}" + data_hosts = [assets_host] -base_host = Rails.configuration.x.web_domain - -assets_host = Rails.configuration.action_controller.asset_host -assets_host ||= host_to_url(base_host) - -media_host = host_to_url(ENV['S3_ALIAS_HOST']) -media_host ||= host_to_url(ENV['S3_CLOUDFRONT_HOST']) -media_host ||= host_to_url(ENV['S3_HOSTNAME']) if ENV['S3_ENABLED'] == 'true' -media_host ||= assets_host - -Rails.application.config.content_security_policy do |p| - p.base_uri :none - p.default_src :none - p.frame_ancestors :none - p.font_src :self, assets_host - p.img_src :self, :https, :data, :blob, assets_host - p.style_src :self, assets_host - p.media_src :self, :https, :data, assets_host - p.frame_src :self, :https - p.manifest_src :self, assets_host - - if Rails.env.development? - webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" } - - p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls - p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host - p.child_src :self, :blob, assets_host - p.worker_src :self, :blob, assets_host + if ENV['S3_ENABLED'] == 'true' + attachments_host = "https://#{ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST'] || ENV['S3_HOSTNAME'] || "s3-#{ENV['S3_REGION'] || 'us-east-1'}.amazonaws.com"}" + attachments_host = "https://#{Addressable::URI.parse(attachments_host).host}" + elsif ENV['SWIFT_ENABLED'] == 'true' + attachments_host = ENV['SWIFT_OBJECT_URL'] + attachments_host = "https://#{Addressable::URI.parse(attachments_host).host}" else - p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url - p.script_src :self, assets_host - p.child_src :self, :blob, assets_host - p.worker_src :self, :blob, assets_host + attachments_host = nil + end + + data_hosts << attachments_host unless attachments_host.nil? + + if ENV['PAPERCLIP_ROOT_URL'] + url = Addressable::URI.parse(assets_host) + ENV['PAPERCLIP_ROOT_URL'] + data_hosts << "https://#{url.host}" + end + + data_hosts.concat(ENV['EXTRA_DATA_HOSTS'].split('|')) if ENV['EXTRA_DATA_HOSTS'] + + data_hosts.uniq! + + Rails.application.config.content_security_policy do |p| + p.base_uri :none + p.default_src :none + p.frame_ancestors :none + p.script_src :self, assets_host + p.font_src :self, assets_host + p.img_src :self, :data, :blob, *data_hosts + p.style_src :self, assets_host + p.media_src :self, :data, *data_hosts + p.frame_src :self, :https + p.child_src :self, :blob, assets_host + p.worker_src :self, :blob, assets_host + p.connect_src :self, :blob, :data, Rails.configuration.x.streaming_api_base_url, *data_hosts + p.manifest_src :self, assets_host end end diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 55f8c9c91..bc782bc76 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -30,5 +30,9 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do headers: :any, methods: [:post], credentials: false + resource '/assets/*', headers: :any, methods: [:get, :head, :options] + resource '/stylesheets/*', headers: :any, methods: [:get, :head, :options] + resource '/javascripts/*', headers: :any, methods: [:get, :head, :options] + resource '/packs/*', headers: :any, methods: [:get, :head, :options] end end diff --git a/config/initializers/locale.rb b/config/initializers/locale.rb new file mode 100644 index 000000000..fed182a71 --- /dev/null +++ b/config/initializers/locale.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +I18n.load_path += Dir[Rails.root.join('app', 'javascript', 'flavours', '*', 'names.{rb,yml}').to_s] +I18n.load_path += Dir[Rails.root.join('app', 'javascript', 'flavours', '*', 'names', '*.{rb,yml}').to_s] +I18n.load_path += Dir[Rails.root.join('app', 'javascript', 'skins', '*', '*', 'names.{rb,yml}').to_s] +I18n.load_path += Dir[Rails.root.join('app', 'javascript', 'skins', '*', '*', 'names', '*.{rb,yml}').to_s] +I18n.load_path += Dir[Rails.root.join('config', 'locales-glitch', '*.{rb,yml}').to_s] diff --git a/config/locales-glitch/en.yml b/config/locales-glitch/en.yml new file mode 100644 index 000000000..7fd0683c9 --- /dev/null +++ b/config/locales-glitch/en.yml @@ -0,0 +1,33 @@ +--- +en: + admin: + settings: + captcha_enabled: + desc_html: This relies on external scripts from hCaptcha, which may be a security and privacy concern. In addition, <strong>this can make the registration process significantly less accessible to some (especially disabled) people</strong>. For these reasons, please consider alternative measures such as approval-based or invite-based registration.<br>Users that have been invited through a limited-use invite will not need to solve a CAPTCHA + title: Require new users to solve a CAPTCHA to confirm their account + enable_keybase: + desc_html: Allow your users to prove their identity via keybase + title: Enable keybase integration + outgoing_spoilers: + desc_html: When federating toots, add this content warning to toots that do not have one. It is useful if your server is specialized in content other servers might want to have under a Content Warning. Media will also be marked as sensitive. + title: Content warning for outgoing toots + hide_followers_count: + desc_html: Do not show followers count on user profiles + title: Hide followers count + show_reblogs_in_public_timelines: + desc_html: Show public boosts of public toots in local and public timelines. + title: Show boosts in public timelines + show_replies_in_public_timelines: + desc_html: In addition to public self-replies (threads), show public replies in local and public timelines. + title: Show replies in public timelines + trending_status_cw: + desc_html: When trending posts are enabled, allow posts with Content Warnings to be eligible. Changes to this setting are not retroactive. + title: Allow posts with Content Warnings to trend + auth: + captcha_confirmation: + hint_html: Just one more step! To confirm your account, this server requires you to solve a CAPTCHA. You can <a href="/about/more">contact the server administrator</a> if you have questions or need assistance with confirming your account. + title: User verification + generic: + use_this: Use this + settings: + flavours: Flavours diff --git a/config/locales-glitch/es.yml b/config/locales-glitch/es.yml new file mode 100644 index 000000000..ad942a0c0 --- /dev/null +++ b/config/locales-glitch/es.yml @@ -0,0 +1,23 @@ +--- +es: + admin: + settings: + enable_keybase: + desc_html: Permite a tus usuarixs comprobar su identidad por medio de keybase + title: Habilitar la integración con keybase + outgoing_spoilers: + desc_html: Cuando los toots federen, agrega esta etiqueta de contenido a los toots que no tengan. Es útil si tu servidor se especializa en contenido que otros servidores desearían tener con una advertencia de contenido. Los medios también se marcarán como sensibles. + title: Advertencia de contenido para los toots salientes + hide_followers_count: + desc_html: No mostrar el conteo de seguidorxs en perfiles de usuarix + title: Ocultar conteo de seguidorxs + show_reblogs_in_public_timelines: + desc_html: Mostrar retoots públicos en las línea de tiempo local y pública. + title: Mostrar retoots en líneas de tiempo públicas + show_replies_in_public_timelines: + desc_html: Además de auto-respuestas públicas (hilos), mostrar respuestas públicas en las línea de tiempo local y pública. + title: Mostrar respuestas en líneas de tiempo públicas + generic: + use_this: Usar + settings: + flavours: Ediciones diff --git a/config/locales-glitch/ja.yml b/config/locales-glitch/ja.yml new file mode 100644 index 000000000..3ecb46ccd --- /dev/null +++ b/config/locales-glitch/ja.yml @@ -0,0 +1,23 @@ +--- +ja: + admin: + settings: + enable_keybase: + desc_html: Keybaseにより身元の証明が可能となります + title: Keybase統合を有効にする + outgoing_spoilers: + desc_html: トゥートが連合される際、閲覧注意としてマークされていないトゥートにこの警告が追加されます。これはあなたのインスタンスが他のインスタンスに警告をして欲しいとされる投稿に特化している場合に便利です。 メディアは閲覧注意にマークされます。 + title: 発信するトゥートへの警告 + hide_followers_count: + desc_html: プロフィールページのフォロワー数を見られないようにします + title: フォロワー数を隠す + show_reblogs_in_public_timelines: + desc_html: ローカルタイムラインと連合タイムラインに公開投稿のブーストを表示します + title: 公開タイムラインにブーストを表示 + show_replies_in_public_timelines: + desc_html: 自分への公開投稿の返信に加えて、すべての公開投稿の返信をローカルタイムラインと連合タイムラインに表示します。 + title: 公開タイムラインに返信を表示 + generic: + use_this: これを使う + settings: + flavours: フレーバー diff --git a/config/locales-glitch/ko.yml b/config/locales-glitch/ko.yml new file mode 100644 index 000000000..aef10f677 --- /dev/null +++ b/config/locales-glitch/ko.yml @@ -0,0 +1,23 @@ +--- +ko: + admin: + settings: + enable_keybase: + desc_html: 사용자들이 키베이스를 통해 개인 신원을 증명할 수 있도록 허용 + title: 키베이스 연동 활성화 + outgoing_spoilers: + desc_html: 게시물들을 연합할 때, 열람주의가 달려있지 않다면 이 열람주의를 추가합니다. 다른 서버들이 열람주의를 하길 원하는 콘텐츠들에 특화된 서버에서 유용합니다. 미디어 또한 민감함으로 설정 됩니다. + title: 나가는 게시물에 대한 열람주의 + hide_followers_count: + desc_html: 사용자 프로필에 팔로워 수를 표시하지 않습니다 + title: 팔로워 수 숨기기 + show_reblogs_in_public_timelines: + desc_html: 공개글의 공개적인 부스트를 로컬과 공개 타임라인에 표시합니다. + title: 부스트를 공개 타임라인에 표시 + show_replies_in_public_timelines: + desc_html: 자기자신에 대한 답글(글타래)와 마찬가지로, 공개적인 답글을 로컬과 공개 타임라인에 표시합니다. + title: 답글을 공개 타임라인에 표시 + generic: + use_this: 사용하기 + settings: + flavours: 풍미 diff --git a/config/locales-glitch/pl.yml b/config/locales-glitch/pl.yml new file mode 100644 index 000000000..3fcdedcf3 --- /dev/null +++ b/config/locales-glitch/pl.yml @@ -0,0 +1,6 @@ +--- +pl: + generic: + use_this: Użyj tego + settings: + flavours: Odmiany diff --git a/config/locales-glitch/simple_form.en.yml b/config/locales-glitch/simple_form.en.yml new file mode 100644 index 000000000..c9ef40996 --- /dev/null +++ b/config/locales-glitch/simple_form.en.yml @@ -0,0 +1,24 @@ +--- +en: + simple_form: + hints: + defaults: + setting_default_content_type_html: When writing toots, assume they are written in raw HTML, unless specified otherwise + setting_default_content_type_markdown: When writing toots, assume they are using Markdown for rich text formatting, unless specified otherwise + setting_default_content_type_plain: When writing toots, assume they are plain text with no special formatting, unless specified otherwise (default Mastodon behavior) + setting_default_language: The language of your toots can be detected automatically, but it's not always accurate + setting_skin: Reskins the selected Mastodon flavour + labels: + defaults: + setting_default_content_type: Default format for toots + setting_default_content_type_html: HTML + setting_default_content_type_markdown: Markdown + setting_default_content_type_plain: Plain text + setting_favourite_modal: Show confirmation dialog before favouriting (applies to Glitch flavour only) + setting_hide_followers_count: Hide your followers count + setting_skin: Skin + setting_system_emoji_font: Use system's default font for emojis (applies to Glitch flavour only) + notification_emails: + trending_tag: New trending tag requires review + trending_link: New trending link requires review + trending_status: New trending post requires review diff --git a/config/locales-glitch/simple_form.es.yml b/config/locales-glitch/simple_form.es.yml new file mode 100644 index 000000000..977775be6 --- /dev/null +++ b/config/locales-glitch/simple_form.es.yml @@ -0,0 +1,20 @@ +--- +es: + simple_form: + hints: + defaults: + setting_default_content_type_html: Al escribir toots, asume que estás escritos en HTML, a menos que se especifique lo contrario + setting_default_content_type_markdown: Al escribir toots, asume que estás usando Markdown para dar formato de texto enriquecido, a menos que se especifique lo contrario + setting_default_content_type_plain: Al escribir toots, asume que estás usando texto sin formato, a menos que se especifique lo contrario (predeterminado de Mastodon) + setting_default_language: El idioma de tus toots se puede detectar automáticamente, pero no siempre es correcto + setting_skin: Cambia el diseño de la edición seleccionada de Mastodon + labels: + defaults: + setting_default_content_type: Formato predeterminado de tus toots + setting_default_content_type_html: HTML + setting_default_content_type_markdown: Markdown + setting_default_content_type_plain: Sin formato + setting_favourite_modal: Mostrar diálogo de confirmación antes de marcar como favorito (sólo aplica a la edición Glich) + setting_hide_followers_count: Ocultar tu conteo de seguidorxs + setting_skin: Diseño + setting_system_emoji_font: Usar la fuente predeterminada del sistema para emojis (sólo aplica a la edición Glitch) diff --git a/config/locales-glitch/simple_form.ja.yml b/config/locales-glitch/simple_form.ja.yml new file mode 100644 index 000000000..dc9e0b579 --- /dev/null +++ b/config/locales-glitch/simple_form.ja.yml @@ -0,0 +1,20 @@ +--- +ja: + simple_form: + hints: + defaults: + setting_default_content_type_html: トゥートを作成するとき特に指定がない限り生のHTMLで書かれているとします + setting_default_content_type_markdown: トゥートを作成するとき特に指定がない限りリッチテキスト形式のマークダウンで書かれているとします + setting_default_content_type_plain: トゥートを作成するとき特に指定がない限りプレーンテキストで書かれているとします(Mastodon既定の動作) + setting_default_language: あなたのトゥートの言語を自動検出しますが必ずしも正確ではありません + setting_skin: 選択したMastodonフレーバーに変更します + labels: + defaults: + setting_default_content_type: 既定のトゥート形式 + setting_default_content_type_html: HTML + setting_default_content_type_markdown: マークダウン + setting_default_content_type_plain: プレーンテキスト + setting_favourite_modal: お気に入りをする前に確認ダイアログを表示する + setting_hide_followers_count: フォロワー数を隠す + setting_skin: スキン + setting_system_emoji_font: 絵文字にシステム既定のフォントを使用する(Glitch Edition フレーバーのみに適用されます) diff --git a/config/locales-glitch/simple_form.ko.yml b/config/locales-glitch/simple_form.ko.yml new file mode 100644 index 000000000..cd9910337 --- /dev/null +++ b/config/locales-glitch/simple_form.ko.yml @@ -0,0 +1,20 @@ +--- +ko: + simple_form: + hints: + defaults: + setting_default_content_type_html: 게시물을 작성할 때, 형식을 지정하지 않았다면, 생 HTML이라고 가정합니다 + setting_default_content_type_markdown: 게시물을 작성할 때, 형식을 지정하지 않았다면, 마크다운이라고 가정합니다 + setting_default_content_type_plain: 게시물을 작성할 때, 형식을 지정하지 않았다면, 일반적인 텍스트라고 가정합니다. (마스토돈의 기본 동작) + setting_default_language: 작성하는 게시물의 언어는 자동으로 설정될 수 있습니다, 하지만 언제나 정확하지는 않습니다 + setting_skin: 선택한 마스토돈 풍미의 스킨을 바꿉니다 + labels: + defaults: + setting_default_content_type: 게시물의 기본 포맷 + setting_default_content_type_html: HTML + setting_default_content_type_markdown: 마크다운 + setting_default_content_type_plain: 일반 텍스트 + setting_favourite_modal: 관심글을 지정할 때 확인 창을 띄웁니다(글리치 풍미에만 적용됨) + setting_hide_followers_count: 내 팔로워 수 숨기기 + setting_skin: 스킨 + setting_system_emoji_font: 에모지에 시스템 기본 폰트 적용하기 (글리치 풍미에만 적용됨) diff --git a/config/locales-glitch/simple_form.pl.yml b/config/locales-glitch/simple_form.pl.yml new file mode 100644 index 000000000..264494c2d --- /dev/null +++ b/config/locales-glitch/simple_form.pl.yml @@ -0,0 +1,10 @@ +--- +pl: + simple_form: + hints: + defaults: + setting_skin: Zmienia wygląd używanej odmiany Mastodona + labels: + defaults: + setting_favourite_modal: Pytaj o potwierdzenie przed dodaniem do ulubionych + setting_skin: Motyw diff --git a/config/locales-glitch/simple_form.zh-CN.yml b/config/locales-glitch/simple_form.zh-CN.yml new file mode 100644 index 000000000..a82617fdb --- /dev/null +++ b/config/locales-glitch/simple_form.zh-CN.yml @@ -0,0 +1,20 @@ +--- +zh-CN: + simple_form: + hints: + defaults: + setting_default_content_type_html: 在撰写嘟文时,除非另有指定,假定它们使用原始 HTML 语言撰写 + setting_default_content_type_markdown: 在撰写嘟文时,除非另有指定,假定它们使用 Markdown 进行富文本格式化 + setting_default_content_type_plain: 在撰写嘟文时,除非另有指定,假定它们是没有特殊格式的纯文本(默认的 Mastodon 行为) + setting_default_language: 你的嘟文语言可以自动检测,但不一定准确 + setting_skin: 更换为所选择的 Mastodon 风味 + labels: + defaults: + setting_default_content_type: 嘟文的默认格式 + setting_default_content_type_html: HTML + setting_default_content_type_markdown: Markdown + setting_default_content_type_plain: 纯文本 + setting_favourite_modal: 在喜欢嘟文前询问我 (仅限于 Glitch 风味) + setting_hide_followers_count: 隐藏你的关注者人数 + setting_skin: 皮肤 + setting_system_emoji_font: 表情符号使用系统默认字体 (仅限于 Glitch 风味) \ No newline at end of file diff --git a/config/locales-glitch/zh-CN.yml b/config/locales-glitch/zh-CN.yml new file mode 100644 index 000000000..37a8307e8 --- /dev/null +++ b/config/locales-glitch/zh-CN.yml @@ -0,0 +1,23 @@ +--- +zh-CN: + admin: + settings: + enable_keybase: + desc_html: 允许你的用户使用 Keybase 证明身份 + title: 启用 Keybase 集成 + outgoing_spoilers: + desc_html: 在联邦化嘟文的时候,将这个内容警告添加到没有内容警告的嘟文中。如果你的服务器专用于其他服务器可能希望有内容警告的内容,它会很有用。媒体也将被标记为敏感。 + title: 对外嘟文的内容警告 + hide_followers_count: + desc_html: 不要在用户资料中显示关注者人数 + title: 隐藏关注者人数 + show_reblogs_in_public_timelines: + desc_html: 在本地和跨站时间线中显示公开嘟文的公开转嘟。 + title: 在公共时间线中显示转嘟 + show_replies_in_public_timelines: + desc_html: 除了公开的自我回复(线程模式),在本地和跨站时间轴中显示公开回复。 + title: 在公共时间轴中显示回复 + generic: + use_this: 使用这个 + settings: + flavours: 风味 diff --git a/config/navigation.rb b/config/navigation.rb index 620f78c57..47993e7a6 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -15,6 +15,12 @@ SimpleNavigation::Configuration.run do |navigation| s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_url end + n.item :flavours, safe_join([fa_icon('paint-brush fw'), t('settings.flavours')]), settings_flavours_url do |flavours| + Themes.instance.flavours.each do |flavour| + flavours.item flavour.to_sym, safe_join([fa_icon('star fw'), t("flavours.#{flavour}.name", default: flavour)]), settings_flavour_url(flavour) + end + end + n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url, if: -> { current_user.functional? } n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? } n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_url, if: -> { current_user.functional? } diff --git a/config/routes.rb b/config/routes.rb index 9e2f7a648..3449fa03d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,7 @@ Rails.application.routes.draw do resource :setup, only: [:show, :update], controller: :setup resource :challenge, only: [:create], controller: :challenges get 'sessions/security_key_options', to: 'sessions#webauthn_options' + post 'captcha_confirmation', to: 'confirmations#confirm_captcha', as: :captcha_confirmation end end @@ -151,6 +152,8 @@ Rails.application.routes.draw do end end + resources :flavours, only: [:index, :show, :update], param: :flavour + resource :delete, only: [:show, :destroy] resource :migration, only: [:show, :create] @@ -389,6 +392,7 @@ Rails.application.routes.draw do end namespace :timelines do + resource :direct, only: :show, controller: :direct resource :home, only: :show, controller: :home resource :public, only: :show, controller: :public resources :tag, only: :show @@ -477,9 +481,10 @@ Rails.application.routes.draw do end end - resources :notifications, only: [:index, :show] do + resources :notifications, only: [:index, :show, :destroy] do collection do post :clear + delete :destroy_multiple end member do diff --git a/config/settings.yml b/config/settings.yml index 06dd2b3f3..51016d322 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -2,7 +2,7 @@ # important settings can be changed from the admin interface. defaults: &defaults - site_title: Mastodon + site_title: 'Mastodon Glitch Edition' site_short_description: '' site_description: '' site_extended_description: '' @@ -13,12 +13,13 @@ defaults: &defaults profile_directory: true closed_registrations_message: '' open_deletion: true + timeline_preview: false min_invite_role: 'admin' - timeline_preview: true show_staff_badge: true default_sensitive: false unfollow_modal: false boost_modal: false + favourite_modal: false delete_modal: true auto_play_gif: false display_media: 'default' @@ -26,16 +27,20 @@ defaults: &defaults preview_sensitive_media: false reduce_motion: false disable_swiping: false - show_application: true + show_application: false system_font_ui: false + system_emoji_font: false noindex: false - theme: 'default' + hide_followers_count: false + flavour: 'glitch' + skin: 'default' aggregate_reblogs: true advanced_layout: false use_blurhash: true use_pending_items: false trends: true trendable_by_default: false + trending_status_cw: true crop_images: true notification_emails: follow: false @@ -47,6 +52,8 @@ defaults: &defaults report: true pending_account: true trending_tag: true + trending_link: false + trending_status: false appeal: true interactions: must_be_follower: false @@ -66,9 +73,14 @@ defaults: &defaults activity_api_enabled: true peers_api_enabled: true show_known_fediverse_at_about_page: true + show_reblogs_in_public_timelines: false + show_replies_in_public_timelines: false + default_content_type: 'text/plain' show_domain_blocks: 'disabled' show_domain_blocks_rationale: 'disabled' + outgoing_spoilers: '' require_invite_text: false + captcha_enabled: false development: <<: *defaults diff --git a/config/themes.yml b/config/themes.yml deleted file mode 100644 index 9c21c9459..000000000 --- a/config/themes.yml +++ /dev/null @@ -1,3 +0,0 @@ -default: styles/application.scss -contrast: styles/contrast.scss -mastodon-light: styles/mastodon-light.scss diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js index 25b6b7abd..55ee06c0c 100644 --- a/config/webpack/configuration.js +++ b/config/webpack/configuration.js @@ -1,15 +1,58 @@ // Common configuration for webpacker loaded from config/webpacker.yml -const { resolve } = require('path'); +const { basename, dirname, extname, join, resolve } = require('path'); const { env } = require('process'); const { load } = require('js-yaml'); -const { readFileSync } = require('fs'); +const { lstatSync, readFileSync } = require('fs'); +const glob = require('glob'); const configPath = resolve('config', 'webpacker.yml'); const settings = load(readFileSync(configPath), 'utf8')[env.RAILS_ENV || env.NODE_ENV]; +const flavourFiles = glob.sync('app/javascript/flavours/*/theme.yml'); +const skinFiles = glob.sync('app/javascript/skins/*/*'); +const flavours = {}; -const themePath = resolve('config', 'themes.yml'); -const themes = load(readFileSync(themePath), 'utf8'); +const core = function () { + const coreFile = resolve('app', 'javascript', 'core', 'theme.yml'); + const data = load(readFileSync(coreFile), 'utf8'); + if (!data.pack_directory) { + data.pack_directory = dirname(coreFile); + } + return data.pack ? data : {}; +}(); + +flavourFiles.forEach((flavourFile) => { + const data = load(readFileSync(flavourFile), 'utf8'); + data.name = basename(dirname(flavourFile)); + data.skin = {}; + if (!data.pack_directory) { + data.pack_directory = dirname(flavourFile); + } + if (data.locales) { + data.locales = join(dirname(flavourFile), data.locales); + } + if (data.pack && typeof data.pack === 'object') { + flavours[data.name] = data; + } +}); + +skinFiles.forEach((skinFile) => { + let skin = basename(skinFile); + const name = basename(dirname(skinFile)); + if (!flavours[name]) { + return; + } + const data = flavours[name].skin; + if (lstatSync(skinFile).isDirectory()) { + data[skin] = {}; + const skinPacks = glob.sync(join(skinFile, '*.{css,scss}')); + skinPacks.forEach((pack) => { + data[skin][basename(pack, extname(pack))] = pack; + }); + } else if ((skin = skin.match(/^(.*)\.s?css$/i))) { + data[skin[1]] = { common: skinFile }; + } +}); const output = { path: resolve('public', settings.public_output_path), @@ -18,7 +61,8 @@ const output = { module.exports = { settings, - themes, + core, + flavours, env: { NODE_ENV: env.NODE_ENV, PUBLIC_OUTPUT_PATH: settings.public_output_path, diff --git a/config/webpack/generateLocalePacks.js b/config/webpack/generateLocalePacks.js index b71cf2ade..09fba4a18 100644 --- a/config/webpack/generateLocalePacks.js +++ b/config/webpack/generateLocalePacks.js @@ -1,52 +1,66 @@ +// A message from upstream: +// ======================== // To avoid adding a lot of boilerplate, locale packs are // automatically generated here. These are written into the tmp/ // directory and then used to generate locale_en.js, locale_fr.js, etc. -const fs = require('fs'); -const path = require('path'); +// Glitch note: +// ============ +// This code has been entirely rewritten to support glitch flavours. +// However, the underlying process is exactly the same. + +const { existsSync, readdirSync, writeFileSync } = require('fs'); +const { join, resolve } = require('path'); const rimraf = require('rimraf'); const mkdirp = require('mkdirp'); +const { flavours } = require('./configuration.js'); + +module.exports = Object.keys(flavours).reduce(function (map, entry) { + const flavour = flavours[entry]; + if (!flavour.locales) { + return map; + } + const locales = readdirSync(flavour.locales).filter( + filename => /\.js(?:on)?$/.test(filename) && !/defaultMessages|whitelist|index/.test(filename) + ); + const outPath = resolve('tmp', 'locales', entry); -const localesJsonPath = path.join(__dirname, '../../app/javascript/mastodon/locales'); -const locales = fs.readdirSync(localesJsonPath).filter(filename => { - return /\.json$/.test(filename) && - !/defaultMessages/.test(filename) && - !/whitelist/.test(filename); -}).map(filename => filename.replace(/\.json$/, '')); - -const outPath = path.join(__dirname, '../../tmp/packs'); - -rimraf.sync(outPath); -mkdirp.sync(outPath); - -const outPaths = []; - -locales.forEach(locale => { - const localePath = path.join(outPath, `locale_${locale}.js`); - const baseLocale = locale.split('-')[0]; // e.g. 'zh-TW' -> 'zh' - const localeDataPath = [ - // first try react-intl - `../../node_modules/react-intl/locale-data/${baseLocale}.js`, - // then check locales/locale-data - `../../app/javascript/mastodon/locales/locale-data/${baseLocale}.js`, - // fall back to English (this is what react-intl does anyway) - '../../node_modules/react-intl/locale-data/en.js', - ].filter(filename => fs.existsSync(path.join(outPath, filename))) - .map(filename => filename.replace(/..\/..\/node_modules\//, ''))[0]; - - const localeContent = `// -// locale_${locale}.js + rimraf.sync(outPath); + mkdirp.sync(outPath); + + locales.forEach(function (locale) { + const localeName = locale.replace(/\.js(?:on)?$/, ''); + const localePath = join(outPath, `${localeName}.js`); + const baseLocale = localeName.split('-')[0]; // e.g. 'zh-TW' -> 'zh' + const localeDataPath = [ + // first try react-intl + `node_modules/react-intl/locale-data/${baseLocale}.js`, + // then check locales/locale-data + `app/javascript/locales/locale-data/${baseLocale}.js`, + // fall back to English (this is what react-intl does anyway) + 'node_modules/react-intl/locale-data/en.js', + ].filter( + filename => existsSync(filename) + ).map( + filename => filename.replace(/(?:node_modules|app\/javascript)\//, '') + )[0]; + const localeContent = `// +// locales/${entry}/${localeName}.js // automatically generated by generateLocalePacks.js // -import messages from '../../app/javascript/mastodon/locales/${locale}.json'; -import localeData from ${JSON.stringify(localeDataPath)}; -import { setLocale } from '../../app/javascript/mastodon/locales'; -setLocale({messages, localeData}); -`; - fs.writeFileSync(localePath, localeContent, 'utf8'); - outPaths.push(localePath); -}); -module.exports = outPaths; +import messages from '../../../${flavour.locales}/${locale.replace(/\.js$/, '')}'; +import localeData from '${localeDataPath}'; +import { setLocale } from 'locales'; +setLocale({ + localeData, + messages, +}); +`; + writeFileSync(localePath, localeContent, 'utf8'); + map[`locales/${entry}/${localeName}`] = localePath; + }); + return map; +}, {}); diff --git a/config/webpack/rules/babel.js b/config/webpack/rules/babel.js index 2fc245c43..4d25748ee 100644 --- a/config/webpack/rules/babel.js +++ b/config/webpack/rules/babel.js @@ -12,6 +12,7 @@ module.exports = { { loader: 'babel-loader', options: { + sourceRoot: 'app/javascript', cacheDirectory: join(settings.cache_path, 'babel-loader'), cacheCompression: env.NODE_ENV === 'production', compact: env.NODE_ENV === 'production', diff --git a/config/webpack/rules/css.js b/config/webpack/rules/css.js index bc1f42c13..6ecfb3164 100644 --- a/config/webpack/rules/css.js +++ b/config/webpack/rules/css.js @@ -20,6 +20,9 @@ module.exports = { { loader: 'sass-loader', options: { + sassOptions: { + includePaths: ['app/javascript'], + }, implementation: require('sass'), sourceMap: true, }, diff --git a/config/webpack/shared.js b/config/webpack/shared.js index 05828aebe..c2a108a89 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -5,33 +5,57 @@ const { basename, dirname, join, relative, resolve } = require('path'); const { sync } = require('glob'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const AssetsManifestPlugin = require('webpack-assets-manifest'); -const extname = require('path-complete-extname'); -const { env, settings, themes, output } = require('./configuration'); +const { env, settings, core, flavours, output } = require('./configuration.js'); const rules = require('./rules'); -const localePackPaths = require('./generateLocalePacks'); +const localePacks = require('./generateLocalePacks'); + +function reducePacks (data, into = {}) { + if (!data.pack) return into; + + for (const entry in data.pack) { + const pack = data.pack[entry]; + if (!pack) continue; + + let packFiles = []; + if (typeof pack === 'string') + packFiles = [pack]; + else if (Array.isArray(pack)) + packFiles = pack; + else + packFiles = [pack.filename]; + + if (packFiles) { + into[data.name ? `flavours/${data.name}/${entry}` : `core/${entry}`] = packFiles.map(packFile => resolve(data.pack_directory, packFile)); + } + } + + if (!data.name) return into; + + for (const skinName in data.skin) { + const skin = data.skin[skinName]; + if (!skin) continue; + + for (const entry in skin) { + const packFile = skin[entry]; + if (!packFile) continue; + + into[`skins/${data.name}/${skinName}/${entry}`] = resolve(packFile); + } + } + + return into; +} + +const entries = Object.assign( + { locales: resolve('app', 'javascript', 'locales') }, + localePacks, + reducePacks(core), + Object.values(flavours).reduce((map, data) => reducePacks(data, map), {}) +); -const extensionGlob = `**/*{${settings.extensions.join(',')}}*`; -const entryPath = join(settings.source_path, settings.source_entry_path); -const packPaths = sync(join(entryPath, extensionGlob)); module.exports = { - entry: Object.assign( - packPaths.reduce((map, entry) => { - const localMap = map; - const namespace = relative(join(entryPath), dirname(entry)); - localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); - return localMap; - }, {}), - localePackPaths.reduce((map, entry) => { - const localMap = map; - localMap[basename(entry, extname(entry, extname(entry)))] = resolve(entry); - return localMap; - }, {}), - Object.keys(themes).reduce((themePaths, name) => { - themePaths[name] = resolve(join(settings.source_path, themes[name])); - return themePaths; - }, {}), - ), + entry: entries, output: { filename: 'js/[name]-[chunkhash].js', @@ -43,7 +67,7 @@ module.exports = { optimization: { runtimeChunk: { - name: 'common', + name: 'locales', }, splitChunks: { cacheGroups: { @@ -51,7 +75,9 @@ module.exports = { vendors: false, common: { name: 'common', - chunks: 'all', + chunks (chunk) { + return !(chunk.name in entries); + }, minChunks: 2, minSize: 0, test: /^(?!.*[\\\/]node_modules[\\\/]react-intl[\\\/]).+$/, diff --git a/config/webpacker.yml b/config/webpacker.yml index 4ad78a190..9accd6152 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -26,6 +26,7 @@ default: &default - .tiff - .ico - .svg + - .gif - .eot - .otf - .ttf |