diff options
Diffstat (limited to 'config')
-rw-r--r-- | config/environments/production.rb | 12 | ||||
-rw-r--r-- | config/i18n-tasks.yml | 1 | ||||
-rw-r--r-- | config/initializers/0_duplicate_migrations.rb | 52 | ||||
-rw-r--r-- | config/initializers/content_security_policy.rb | 74 | ||||
-rw-r--r-- | config/initializers/cors.rb | 4 | ||||
-rw-r--r-- | config/initializers/locale.rb | 6 | ||||
-rw-r--r-- | config/locales/en.yml | 15 | ||||
-rw-r--r-- | config/locales/ja.yml | 2 | ||||
-rw-r--r-- | config/locales/pl.yml | 2 | ||||
-rw-r--r-- | config/locales/simple_form.en.yml | 15 | ||||
-rw-r--r-- | config/locales/simple_form.ja.yml | 1 | ||||
-rw-r--r-- | config/locales/simple_form.pl.yml | 3 | ||||
-rw-r--r-- | config/navigation.rb | 6 | ||||
-rw-r--r-- | config/routes.rb | 12 | ||||
-rw-r--r-- | config/settings.yml | 16 | ||||
-rw-r--r-- | config/themes.yml | 3 | ||||
-rw-r--r-- | config/webpack/configuration.js | 57 | ||||
-rw-r--r-- | config/webpack/generateLocalePacks.js | 94 | ||||
-rw-r--r-- | config/webpack/rules/babel.js | 1 | ||||
-rw-r--r-- | config/webpack/rules/css.js | 3 | ||||
-rw-r--r-- | config/webpack/shared.js | 75 | ||||
-rw-r--r-- | config/webpacker.yml | 1 |
22 files changed, 335 insertions, 120 deletions
diff --git a/config/environments/production.rb b/config/environments/production.rb index 29d6194dd..d617a297a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -104,10 +104,14 @@ 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' => '1; mode=block', + 'Server' => 'Mastodon', + #'X-Frame-Options' => 'DENY', + #'X-Content-Type-Options' => 'nosniff', + #'X-XSS-Protection' => '1; mode=block', + #'Referrer-Policy' => 'same-origin', + #'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload', + '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 c2d6a7838..1e223fbde 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.*' ignore_inconsistent_interpolations: diff --git a/config/initializers/0_duplicate_migrations.rb b/config/initializers/0_duplicate_migrations.rb new file mode 100644 index 000000000..194aff70c --- /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, 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, 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 7dcc028ab..dbe5894ae 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 - -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 Rails.env.production? + assets_host = Rails.configuration.action_controller.asset_host || "https://#{ENV['WEB_DOMAIN'] || ENV['LOCAL_DOMAIN']}" + data_hosts = [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, "pool.jortage.com", "blob.jortage.com", "s3-us-east-2.amazonaws.com" + p.style_src :self, assets_host + p.media_src :self, :data, *data_hosts, "pool.jortage.com", "blob.jortage.com", "s3-us-east-2.amazonaws.com" + 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..04ed31646 --- /dev/null +++ b/config/initializers/locale.rb @@ -0,0 +1,6 @@ +# 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] diff --git a/config/locales/en.yml b/config/locales/en.yml index 116db4498..9d0e2e092 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -332,6 +332,7 @@ en: feature_timeline_preview: Timeline preview features: Features hidden_service: Federation with hidden services + keybase: Keybase integration open_reports: open reports pending_tags: hashtags waiting for review pending_users: users waiting for review @@ -513,9 +514,15 @@ en: title: Show rationale enable_bootstrap_timeline_accounts: title: Enable default follows for new users + enable_keybase: + desc_html: Allow your users to prove their identity via keybase + title: Enable keybase integration hero: desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail title: Hero image + hide_followers_count: + desc_html: Do not show followers count on user profiles + title: Hide followers count mascot: desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot title: Mascot image @@ -547,6 +554,12 @@ en: show_known_fediverse_at_about_page: desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content title: Include federated content on unauthenticated public timeline page + 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 show_staff_badge: desc_html: Show a staff badge on a user page title: Show staff badge @@ -829,6 +842,7 @@ en: no_batch_actions_available: No batch actions available on this page order_by: Order by save_changes: Save changes + use_this: Use this validation_errors: one: Something isn't quite right yet! Please review the error below other: Something isn't quite right yet! Please review %{count} errors below @@ -1095,6 +1109,7 @@ en: edit_profile: Edit profile export: Data export featured_tags: Featured hashtags + flavours: Flavours identity_proofs: Identity proofs import: Import import_and_export: Import and export diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 8a8baf764..fe9ebb263 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -821,6 +821,7 @@ ja: no_batch_actions_available: このページに一括操作はありません order_by: 並び順 save_changes: 変更を保存 + use_this: これを使う validation_errors: other: エラーが発生しました! 以下の%{count}個のエラーを確認してください html_validator: @@ -1082,6 +1083,7 @@ ja: edit_profile: プロフィールを編集 export: データのエクスポート featured_tags: 注目のハッシュタグ + flavours: フレーバー identity_proofs: Identity proofs import: データのインポート import_and_export: インポート・エクスポート diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e17986772..3fe2ce4af 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -845,6 +845,7 @@ pl: no_batch_actions_available: Brak akcji wsadowych dostępnych na tej stronie order_by: Uporządkuj według save_changes: Zapisz zmiany + use_this: Użyj tego validation_errors: few: Coś jest wciąż nie tak! Przejrzyj %{count} poniższe błędy many: Coś jest wciąż nie tak! Przejrzyj %{count} poniższych błędów @@ -1116,6 +1117,7 @@ pl: edit_profile: Edytuj profil export: Eksportowanie danych featured_tags: Wyróżnione hashtagi + flavours: Odmiany identity_proofs: Dowody tożsamości import: Importowanie danych import_and_export: Import i eksport diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index fd56a35bf..49a6007e9 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -41,6 +41,10 @@ en: phrase: Will be matched regardless of casing in text or content warning of a toot scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. setting_aggregate_reblogs: Do not show new boosts for toots that have been recently boosted (only affects newly-received boosts) + 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_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media @@ -48,6 +52,7 @@ en: setting_hide_network: Who you follow and who follows you will not be shown on your profile setting_noindex: Affects your public profile and status pages setting_show_application: The application you use to toot will be displayed in the detailed view of your toots + setting_skin: Reskins the selected Mastodon flavour setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed username: Your username will be unique on %{domain} @@ -64,7 +69,7 @@ en: imports: data: CSV file exported from another Mastodon server invite_request: - text: This will help us review your application + text: 'If you are interested in signing up, please put whether you are a plural system or singlet in the "Why do you want to join" box. This is purely to ward off spam bots. If this is not answered, your request to join the instance will be rejected.' sessions: otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:' tag: @@ -131,6 +136,10 @@ en: setting_auto_play_gif: Auto-play animated GIFs setting_boost_modal: Show confirmation dialog before boosting setting_crop_images: Crop images in non-expanded toots to 16x9 + 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_default_language: Posting language setting_default_privacy: Posting privacy setting_default_sensitive: Always mark media as sensitive @@ -140,10 +149,14 @@ en: setting_display_media_hide_all: Hide all setting_display_media_show_all: Show all setting_expand_spoilers: Always expand toots marked with content warnings + setting_favourite_modal: Show confirmation dialog before favouriting (applies to Glitch flavour only) + setting_hide_followers_count: Hide your followers count setting_hide_network: Hide your network setting_noindex: Opt-out of search engine indexing setting_reduce_motion: Reduce motion in animations setting_show_application: Disclose application used to send toots + setting_skin: Skin + setting_system_emoji_font: Use system's default font for emojis (applies to Glitch flavour only) setting_system_font_ui: Use system's default font setting_theme: Site theme setting_trends: Show today's trends diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index a14dc3e04..eb9c5455b 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -140,6 +140,7 @@ ja: setting_display_media_hide_all: 非表示 setting_display_media_show_all: 表示 setting_expand_spoilers: 閲覧注意としてマークされたトゥートを常に展開する + setting_favourite_modal: お気に入りをする前に確認ダイアログを表示する setting_hide_network: 繋がりを隠す setting_noindex: 検索エンジンによるインデックスを拒否する setting_reduce_motion: アニメーションの動きを減らす diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 77d02cf17..10e9f3d2b 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -48,6 +48,7 @@ pl: setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany + setting_skin: Zmienia wygląd używanej odmiany Mastodona setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain} @@ -140,10 +141,12 @@ pl: setting_display_media_hide_all: Ukryj wszystko setting_display_media_show_all: Pokaż wszystko setting_expand_spoilers: Zawsze rozwijaj wpisy oznaczone ostrzeżeniem o zawartości + setting_favourite_modal: Pytaj o potwierdzenie przed dodaniem do ulubionych setting_hide_network: Ukryj swoją sieć setting_noindex: Nie indeksuj mojego profilu w wyszukiwarkach internetowych setting_reduce_motion: Ogranicz ruch w animacjach setting_show_application: Informuj o aplikacji z której wysłano wpisy + setting_skin: Motyw setting_system_font_ui: Używaj domyślnej czcionki systemu setting_theme: Motyw strony setting_trends: Pokazuj dzisiejsze „Na czasie” diff --git a/config/navigation.rb b/config/navigation.rb index 8fd296d5a..bd172f25f 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -16,6 +16,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? } diff --git a/config/routes.rb b/config/routes.rb index 920a48fe7..42f2f0f7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -138,6 +138,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] @@ -317,6 +319,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 @@ -347,7 +350,11 @@ Rails.application.routes.draw do resources :media, only: [:create, :update, :show] resources :blocks, only: [:index] - resources :mutes, only: [:index] + resources :mutes, only: [:index] do + collection do + get 'details' + end + end resources :favourites, only: [:index] resources :bookmarks, only: [:index] resources :reports, only: [:create] @@ -377,9 +384,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 002473643..00a4421e4 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: 'dev.glitch.social' site_short_description: '' site_description: '' site_extended_description: '' @@ -13,23 +13,28 @@ 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 hide_network: false unfollow_modal: false boost_modal: false + favourite_modal: false delete_modal: true auto_play_gif: false display_media: 'default' expand_spoilers: false preview_sensitive_media: false reduce_motion: false - show_application: true + show_application: false system_font_ui: false + system_emoji_font: false noindex: false - theme: 'default' + hide_followers_count: false + enable_keybase: true + flavour: 'glitch' + skin: 'default' aggregate_reblogs: true advanced_layout: false use_blurhash: true @@ -66,6 +71,9 @@ 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' spam_check_enabled: true show_domain_blocks: 'disabled' show_domain_blocks_rationale: 'disabled' 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 80a094c72..926af9b39 100644 --- a/config/webpack/configuration.js +++ b/config/webpack/configuration.js @@ -1,15 +1,61 @@ // 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 { safeLoad } = require('js-yaml'); -const { readFileSync } = require('fs'); +const { lstatSync, readFileSync } = require('fs'); +const glob = require('glob'); const configPath = resolve('config', 'webpacker.yml'); const settings = safeLoad(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 = safeLoad(readFileSync(themePath), 'utf8'); +const core = function () { + const coreFile = resolve('app', 'javascript', 'core', 'theme.yml'); + const data = safeLoad(readFileSync(coreFile), 'utf8'); + if (!data.pack_directory) { + data.pack_directory = dirname(coreFile); + } + return data.pack ? data : {}; +}(); + +for (let i = 0; i < flavourFiles.length; i++) { + const flavourFile = flavourFiles[i]; + const data = safeLoad(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; + } +} + +for (let i = 0; i < skinFiles.length; i++) { + const skinFile = skinFiles[i]; + let skin = basename(skinFile); + const name = basename(dirname(skinFile)); + if (!flavours[name]) { + continue; + } + const data = flavours[name].skin; + if (lstatSync(skinFile).isDirectory()) { + data[skin] = {}; + const skinPacks = glob.sync(join(skinFile, '*.{css,scss}')); + for (let j = 0; j < skinPacks.length; j++) { + const pack = skinPacks[j]; + data[skin][basename(pack, extname(pack))] = pack; + } + } else if ((skin = skin.match(/^(.*)\.s?css$/i))) { + data[skin[1]] = { common: skinFile }; + } +} function removeOuterSlashes(string) { return string.replace(/^\/*/, '').replace(/\/*$/, ''); @@ -31,7 +77,8 @@ const output = { module.exports = { settings, - themes, + core, + flavours, env: { CDN_HOST: env.CDN_HOST, NODE_ENV: env.NODE_ENV, 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 81c02acd4..08526957b 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -6,33 +6,56 @@ const { sync } = require('glob'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const AssetsManifestPlugin = require('webpack-assets-manifest'); const CopyPlugin = require('copy-webpack-plugin'); -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; + } + Object.keys(data.pack).reduce((map, entry) => { + const pack = data.pack[entry]; + if (!pack) { + return map; + } + const packFile = typeof pack === 'string' ? pack : pack.filename; + if (packFile) { + map[data.name ? `flavours/${data.name}/${entry}` : `core/${entry}`] = resolve(data.pack_directory, packFile); + } + return map; + }, into); + if (data.name) { + Object.keys(data.skin).reduce((map, entry) => { + const skin = data.skin[entry]; + const skinName = entry; + if (!skin) { + return map; + } + Object.keys(skin).reduce((map, entry) => { + const packFile = skin[entry]; + if (!packFile) { + return map; + } + map[`skins/${data.name}/${skinName}/${entry}`] = resolve(packFile); + return map; + }, into); + return map; + }, into); + } + return into; +} + +const entries = Object.assign( + { locales: resolve('app', 'javascript', 'locales') }, + localePacks, + reducePacks(core), + Object.keys(flavours).reduce((map, entry) => reducePacks(flavours[entry], 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', @@ -44,7 +67,7 @@ module.exports = { optimization: { runtimeChunk: { - name: 'common', + name: 'locales', }, splitChunks: { cacheGroups: { @@ -52,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 |