# frozen_string_literal: true
class AboutController < ApplicationController
+ before_action :set_pack
layout 'public'
- before_action :require_open_federation!, only: [:show, :more]
+ before_action :require_open_federation!, only: [:show, :more, :blocks]
+ before_action :check_blocklist_enabled, only: [:blocks]
+ before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required?
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
- before_action :set_expires_in
+ before_action :set_expires_in, only: [:show, :more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]
class HomeController < ApplicationController
before_action :authenticate_user!
+
+ before_action :set_pack
before_action :set_referrer_policy_header
- before_action :set_initial_state_json
def index
@body_classes = 'app-body'
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
end
- def set_initial_state_json
- serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
- @initial_state_json = serializable_resource.to_json
- end
-
- def initial_state_params
- {
- settings: Web::Setting.find_by(user: current_user)&.data || {},
- push_subscription: current_account.user.web_push_subscription(current_session),
- current_account: current_account,
- token: current_session.token,
- admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
- }
- end
-
+ def set_pack
+ use_pack 'home'
+ end
+
def default_redirect_path
if request.path.start_with?('/web') || whitelist_mode?
new_user_session_path
layout 'modal'
before_action :authenticate_user!
+ before_action :set_pack
before_action :set_body_classes
- def show
- serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
- @initial_state_json = serializable_resource.to_json
- end
+ def show; end
private
- def initial_state_params
- text = [params[:title], params[:text], params[:url]].compact.join(' ')
-
- {
- settings: Web::Setting.find_by(user: current_user)&.data || {},
- push_subscription: current_account.user.web_push_subscription(current_session),
- current_account: current_account,
- token: current_session.token,
- admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
- text: text,
- }
- end
-
+ def set_pack
+ use_pack 'share'
+ end
+
def set_body_classes
@body_classes = 'modal-layout compose-standalone'
end
def show
respond_to do |format|
format.html do
+ use_pack 'about'
expires_in 0, public: true
-
- @initial_state_json = ActiveModelSerializers::SerializableResource.new(
- InitialStatePresenter.new(settings: {}, token: current_session&.token),
- serializer: InitialStateSerializer
- ).to_json
end
format.rss do
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.profile_directory": "ディレクトリ",
"navigation_bar.public_timeline": "連合タイムライン",
+ "navigation_bar.misc": "その他",
"navigation_bar.security": "セキュリティ",
+ "notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
"notification.follow": "{name}さんにフォローされました",
"notification.mention": "{name}さんがあなたに返信しました",
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
- });
-
- delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
- if (button !== 0) {
- return true;
- }
- window.location.href = target.href;
- return false;
- });
-
- delegate(document, '.status__content__spoiler-link', 'click', function() {
- const contentEl = this.parentNode.parentNode.querySelector('.e-content');
-
- if (contentEl.style.display === 'block') {
- contentEl.style.display = 'none';
- this.parentNode.style.marginBottom = 0;
- } else {
- contentEl.style.display = 'block';
- this.parentNode.style.marginBottom = null;
- }
-
- return false;
- });
-
- delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
- e.preventDefault();
-
- const classList = this.firstElementChild.classList;
- classList.toggle('fa-chevron-down');
- classList.toggle('fa-chevron-up');
- this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
- });
-
- delegate(document, '.modal-button', 'click', e => {
- e.preventDefault();
-
- let href;
-
- if (e.target.nodeName !== 'A') {
- href = e.target.parentNode.href;
- } else {
- href = e.target.href;
- }
-
- window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
- });
-
- delegate(document, '#account_display_name', 'input', ({ target }) => {
- const name = document.querySelector('.card .display-name strong');
- if (name) {
- if (target.value) {
- name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
- } else {
- name.textContent = document.querySelector('#default_account_display_name').textContent;
- }
- }
- });
-
- delegate(document, '#account_avatar', 'change', ({ target }) => {
- const avatar = document.querySelector('.card .avatar img');
- const [file] = target.files || [];
- const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
-
- avatar.src = url;
- });
-
- const getProfileAvatarAnimationHandler = (swapTo) => {
- //animate avatar gifs on the profile page when moused over
- return ({ target }) => {
- const swapSrc = target.getAttribute(swapTo);
- //only change the img source if autoplay is off and the image src is actually different
- if(target.getAttribute('data-autoplay') !== 'true' && target.src !== swapSrc) {
- target.src = swapSrc;
- }
- };
- };
-
- delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original'));
-
- delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static'));
-
- delegate(document, '#account_header', 'change', ({ target }) => {
- const header = document.querySelector('.card .card__img img');
- const [file] = target.files || [];
- const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
-
- header.src = url;
- });
+
- delegate(document, '#account_locked', 'change', ({ target }) => {
- const lock = document.querySelector('.card .display-name i');
++ delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
++ e.preventDefault();
+
- if (target.checked) {
- lock.style.display = 'inline';
- } else {
- lock.style.display = 'none';
- }
- });
-
- delegate(document, '.input-copy input', 'click', ({ target }) => {
- target.focus();
- target.select();
- target.setSelectionRange(0, target.value.length);
- });
-
- delegate(document, '.input-copy button', 'click', ({ target }) => {
- const input = target.parentNode.querySelector('.input-copy__wrapper input');
-
- const oldReadOnly = input.readonly;
-
- input.readonly = false;
- input.focus();
- input.select();
- input.setSelectionRange(0, input.value.length);
-
- try {
- if (document.execCommand('copy')) {
- input.blur();
- target.parentNode.classList.add('copied');
-
- setTimeout(() => {
- target.parentNode.classList.remove('copied');
- }, 700);
- }
- } catch (err) {
- console.error(err);
- }
-
- input.readonly = oldReadOnly;
++ const classList = this.firstElementChild.classList;
++ classList.toggle('fa-chevron-down');
++ classList.toggle('fa-chevron-up');
++ this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
++ });
});
}
thumbnail
hero
mascot
+ show_reblogs_in_public_timelines
+ show_replies_in_public_timelines
spam_check_enabled
trends
+ show_domain_blocks
+ show_domain_blocks_rationale
).freeze
BOOLEAN_KEYS = %i(
end
end
+ def marked_local_only?
+ # match both with and without U+FE0F (the emoji variation selector)
+ /#{local_only_emoji}\ufe0f?\z/.match?(content)
+ end
+
+ def local_only_emoji
+ '👁'
+ end
+
+ def status_stat
+ super || build_status_stat
+ end
+
private
def update_status_stat!(attrs)
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
store[:is_staff] = object.current_account.user.staff?
store[:trends] = Setting.trends && object.current_account.user.setting_trends
+ store[:default_content_type] = object.current_account.user.setting_default_content_type
+ store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font
+ else
+ store[:auto_play_gif] = Setting.auto_play_gif
+ store[:display_media] = Setting.display_media
+ store[:reduce_motion] = Setting.reduce_motion
+ store[:use_blurhash] = Setting.use_blurhash
end
store
= preload_link_tag asset_pack_path('features/notifications.js'), crossorigin: 'anonymous'
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
- %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
+ = render_initial_state
- = javascript_pack_tag 'application', integrity: true, crossorigin: 'anonymous'
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
%noscript
- = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
+ - content_for :header_tags do
+ = render_initial_state
+
- content_for :content do
.public-layout
- unless @hide_navbar
- content_for :header_tags do
%meta{ name: 'robots', content: 'noindex' }/
- %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
- = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
.page-header
%h1= t('about.see_whats_happening')
- content_for :header_tags do
- %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
+ = render_initial_state
- = javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous'
#mastodon-compose{ data: { props: Oj.dump(default_props) } }
%meta{ name: 'robots', content: 'noindex' }/
%link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
- %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
- = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
= render 'og'
.page-header
# 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
+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
+ attachments_host = nil
+ end
-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, :unsafe_inline, assets_host
- p.media_src :self, :https, :data, assets_host
- p.frame_src :self, :https
- p.manifest_src :self, assets_host
+ data_hosts << attachments_host unless attachments_host.nil?
- if Rails.env.development?
- webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" }
+ if ENV['PAPERCLIP_ROOT_URL']
+ url = Addressable::URI.parse(assets_host) + ENV['PAPERCLIP_ROOT_URL']
+ data_hosts << "https://#{url.host}"
+ end
- p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
- p.script_src :self, :blob, :unsafe_inline, :unsafe_eval, assets_host
- else
- p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url
- p.script_src :self, :blob, assets_host
+ 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, :unsafe_inline, assets_host
+ p.media_src :self, :data, *data_hosts
+ p.frame_src :self, :https
- p.worker_src :self, assets_host
++ p.worker_src :self, :blob, assets_host
+ p.connect_src :self, :blob, Rails.configuration.x.streaming_api_base_url, *data_hosts
+ p.manifest_src :self, assets_host
end
end
custom_css:
desc_html: Modify the look with CSS loaded on every page
title: Custom CSS
+ domain_blocks:
+ all: To everyone
+ disabled: To no one
+ title: Show domain blocks
+ users: To logged-in local users
+ domain_blocks_rationale:
+ title: Show rationale
+ 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
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'
development:
<<: *defaults
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 CopyPlugin = require('copy-webpack-plugin');
+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',
"escape-html": "^1.0.3",
"exif-js": "^2.3.0",
"express": "^4.17.1",
- "file-loader": "^4.1.0",
+ "file-loader": "^4.2.0",
+ "favico.js": "^0.3.10",
"font-awesome": "^4.7.0",
"glob": "^7.1.1",
"http-link-header": "^1.0.2",