Allow admins to configure instance favicon and logo (#30040)

This commit is contained in:
Fawaz Farid 2024-05-06 18:06:52 +03:00 committed by GitHub
parent b152f936c1
commit bc24c4792d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 87 additions and 19 deletions

View file

@ -240,6 +240,13 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end end
def site_icon_path(type, size = '48')
icon = SiteUpload.find_by(var: type)
return nil unless icon
icon.file.url(size)
end
private private
def storage_host_var def storage_host_var

View file

@ -37,6 +37,8 @@ class Form::AdminSettings
status_page_url status_page_url
captcha_enabled captcha_enabled
authorized_fetch authorized_fetch
app_icon
favicon
).freeze ).freeze
INTEGER_KEYS = %i( INTEGER_KEYS = %i(
@ -63,6 +65,8 @@ class Form::AdminSettings
UPLOAD_KEYS = %i( UPLOAD_KEYS = %i(
thumbnail thumbnail
mascot mascot
app_icon
favicon
).freeze ).freeze
OVERRIDEN_SETTINGS = { OVERRIDEN_SETTINGS = {

View file

@ -19,7 +19,15 @@
class SiteUpload < ApplicationRecord class SiteUpload < ApplicationRecord
include Attachmentable include Attachmentable
FAVICON_SIZES = [16, 32, 48].freeze
APPLE_ICON_SIZES = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024].freeze
ANDROID_ICON_SIZES = [36, 48, 72, 96, 144, 192, 256, 384, 512].freeze
APP_ICON_SIZES = (APPLE_ICON_SIZES + ANDROID_ICON_SIZES).uniq.freeze
STYLES = { STYLES = {
app_icon: APP_ICON_SIZES.each_with_object({}) { |size, hash| hash[size.to_s.to_sym] = "#{size}x#{size}#" }.freeze,
favicon: FAVICON_SIZES.each_with_object({}) { |size, hash| hash[size.to_s.to_sym] = "#{size}x#{size}#" }.freeze,
thumbnail: { thumbnail: {
'@1x': { '@1x': {
format: 'png', format: 'png',

View file

@ -1,21 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class ManifestSerializer < ActiveModel::Serializer class ManifestSerializer < ActiveModel::Serializer
include ApplicationHelper
include RoutingHelper include RoutingHelper
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
ICON_SIZES = %w(
36
48
72
96
144
192
256
384
512
).freeze
attributes :id, :name, :short_name, attributes :id, :name, :short_name,
:icons, :theme_color, :background_color, :icons, :theme_color, :background_color,
:display, :start_url, :scope, :display, :start_url, :scope,
@ -37,9 +26,12 @@ class ManifestSerializer < ActiveModel::Serializer
end end
def icons def icons
ICON_SIZES.map do |size| SiteUpload::ANDROID_ICON_SIZES.map do |size|
src = site_icon_path('app_icon', size.to_i)
src = URI.join(root_url, src).to_s if src.present?
{ {
src: frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), src: src || frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"),
sizes: "#{size}x#{size}", sizes: "#{size}x#{size}",
type: 'image/png', type: 'image/png',
purpose: 'any maskable', purpose: 'any maskable',

View file

@ -40,5 +40,33 @@
= fa_icon 'trash fw' = fa_icon 'trash fw'
= t('admin.site_uploads.delete') = t('admin.site_uploads.delete')
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :favicon,
as: :file,
input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') },
wrapper: :with_block_label
.fields-row__column.fields-row__column-6.fields-group
- if @admin_settings.favicon.persisted?
= image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail'
= link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do
= fa_icon 'trash fw'
= t('admin.site_uploads.delete')
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :app_icon,
as: :file,
input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') },
wrapper: :with_block_label
.fields-row__column.fields-row__column-6.fields-group
- if @admin_settings.app_icon.persisted?
= image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail'
= link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do
= fa_icon 'trash fw'
= t('admin.site_uploads.delete')
.actions .actions
= f.button :button, t('generic.save_changes'), type: :submit = f.button :button, t('generic.save_changes'), type: :submit

View file

@ -11,13 +11,13 @@
- if storage_host? - if storage_host?
%link{ rel: 'dns-prefetch', href: storage_host }/ %link{ rel: 'dns-prefetch', href: storage_host }/
%link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/ %link{ rel: 'icon', href: site_icon_path('favicon') || '/favicon.ico', type: 'image/x-icon' }/
- %w(16 32 48).each do |size| - SiteUpload::FAVICON_SIZES.each do |size|
%link{ rel: 'icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ %link{ rel: 'icon', sizes: "#{size}x#{size}", href: site_icon_path('favicon', size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/
- %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| - SiteUpload::APPLE_ICON_SIZES.each do |size|
%link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: site_icon_path('app_icon', size.to_i) || frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/
%link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/
%link{ rel: 'manifest', href: manifest_path(format: :json) }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/

View file

@ -77,9 +77,12 @@ en-GB:
warn: Hide the filtered content behind a warning mentioning the filter's title warn: Hide the filtered content behind a warning mentioning the filter's title
form_admin_settings: form_admin_settings:
activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets
app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon.
backups_retention_period: Keep generated user archives for the specified number of days.
bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
closed_registrations_message: Displayed when sign-ups are closed closed_registrations_message: Displayed when sign-ups are closed
custom_css: You can apply custom styles on the web version of Mastodon. custom_css: You can apply custom styles on the web version of Mastodon.
favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon.
mascot: Overrides the illustration in the advanced web interface. mascot: Overrides the illustration in the advanced web interface.
peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense.
profile_directory: The profile directory lists all users who have opted-in to be discoverable. profile_directory: The profile directory lists all users who have opted-in to be discoverable.

View file

@ -77,11 +77,13 @@ en:
warn: Hide the filtered content behind a warning mentioning the filter's title warn: Hide the filtered content behind a warning mentioning the filter's title
form_admin_settings: form_admin_settings:
activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets activity_api_enabled: Counts of locally published posts, active users, and new registrations in weekly buckets
app_icon: WEBP, PNG, GIF or JPG. Overrides the default app icon on mobile devices with a custom icon.
backups_retention_period: Users have the ability to generate archives of their posts to download later. When set to a positive value, these archives will be automatically deleted from your storage after the specified number of days. backups_retention_period: Users have the ability to generate archives of their posts to download later. When set to a positive value, these archives will be automatically deleted from your storage after the specified number of days.
bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
closed_registrations_message: Displayed when sign-ups are closed closed_registrations_message: Displayed when sign-ups are closed
content_cache_retention_period: All posts from other servers (including boosts and replies) will be deleted after the specified number of days, without regard to any local user interaction with those posts. This includes posts where a local user has marked it as bookmarks or favorites. Private mentions between users from different instances will also be lost and impossible to restore. Use of this setting is intended for special purpose instances and breaks many user expectations when implemented for general purpose use. content_cache_retention_period: All posts from other servers (including boosts and replies) will be deleted after the specified number of days, without regard to any local user interaction with those posts. This includes posts where a local user has marked it as bookmarks or favorites. Private mentions between users from different instances will also be lost and impossible to restore. Use of this setting is intended for special purpose instances and breaks many user expectations when implemented for general purpose use.
custom_css: You can apply custom styles on the web version of Mastodon. custom_css: You can apply custom styles on the web version of Mastodon.
favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon.
mascot: Overrides the illustration in the advanced web interface. mascot: Overrides the illustration in the advanced web interface.
media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time. media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time.
peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense.

View file

@ -285,4 +285,28 @@ describe ApplicationHelper do
end end
end end
end end
describe '#site_icon_path' do
context 'when an icon exists' do
let!(:favicon) { Fabricate(:site_upload, var: 'favicon') }
it 'returns the URL of the icon' do
expect(helper.site_icon_path('favicon')).to eq(favicon.file.url('48'))
end
it 'returns the URL of the icon with size parameter' do
expect(helper.site_icon_path('favicon', 16)).to eq(favicon.file.url('16'))
end
end
context 'when an icon does not exist' do
it 'returns nil' do
expect(helper.site_icon_path('favicon')).to be_nil
end
it 'returns nil with size parameter' do
expect(helper.site_icon_path('favicon', 16)).to be_nil
end
end
end
end end