FEATURE: support custom icons in themes (#7155)
* First take * Add support for sprites in themes Automatically register any custom icons added via themes or plugins * Fix theme sprite caching * Simplify test * Update lib/svg_sprite/svg_sprite.rb Co-Authored-By: pmusaraj <pmusaraj@gmail.com> * Fix /svg-sprite/search request
This commit is contained in:
parent
32db3ac228
commit
d6d4a5ba4a
|
@ -27,13 +27,16 @@ class SvgSpriteController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def search
|
def search
|
||||||
keyword = params.require(:keyword)
|
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
|
||||||
data = SvgSprite.search(keyword)
|
|
||||||
|
|
||||||
if data.blank?
|
keyword = params.require(:keyword)
|
||||||
render body: nil, status: 404
|
data = SvgSprite.search(keyword)
|
||||||
else
|
|
||||||
render plain: data.inspect, disposition: nil, content_type: 'text/plain'
|
if data.blank?
|
||||||
|
render body: nil, status: 404
|
||||||
|
else
|
||||||
|
render plain: data.inspect, disposition: nil, content_type: 'text/plain'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ class ThemeField < ActiveRecord::Base
|
||||||
|
|
||||||
after_commit do |field|
|
after_commit do |field|
|
||||||
SvgSprite.expire_cache if field.target_id == Theme.targets[:settings]
|
SvgSprite.expire_cache if field.target_id == Theme.targets[:settings]
|
||||||
|
SvgSprite.expire_cache if field.name == SvgSprite.theme_sprite_variable_name
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :find_by_theme_ids, ->(theme_ids) {
|
scope :find_by_theme_ids, ->(theme_ids) {
|
||||||
|
|
|
@ -189,8 +189,28 @@ module SvgSprite
|
||||||
|
|
||||||
FA_ICON_MAP = { 'far fa-' => 'far-', 'fab fa-' => 'fab-', 'fas fa-' => '', 'fa-' => '' }
|
FA_ICON_MAP = { 'far fa-' => 'far-', 'fab fa-' => 'fab-', 'fas fa-' => '', 'fa-' => '' }
|
||||||
|
|
||||||
SVG_SPRITE_PATHS = Dir.glob(["#{Rails.root}/vendor/assets/svg-icons/**/*.svg",
|
CORE_SVG_SPRITES = Dir.glob("#{Rails.root}/vendor/assets/svg-icons/**/*.svg")
|
||||||
"#{Rails.root}/plugins/*/svg-icons/*.svg"])
|
|
||||||
|
THEME_SPRITE_VAR_NAME = "icons-sprite"
|
||||||
|
|
||||||
|
def self.custom_svg_sprites(theme_ids = [])
|
||||||
|
custom_sprite_paths = Dir.glob("#{Rails.root}/plugins/*/svg-icons/*.svg")
|
||||||
|
|
||||||
|
ThemeField.where(type_id: ThemeField.types[:theme_upload_var], name: THEME_SPRITE_VAR_NAME, theme_id: Theme.transform_ids(theme_ids))
|
||||||
|
.pluck(:upload_id).each do |upload_id|
|
||||||
|
|
||||||
|
upload = Upload.find(upload_id)
|
||||||
|
original_path = Discourse.store.path_for(upload)
|
||||||
|
if original_path.blank?
|
||||||
|
external_copy = Discourse.store.download(upload) rescue nil
|
||||||
|
original_path = external_copy.try(:path)
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_sprite_paths << Discourse.store.path_for(upload) if original_path.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_sprite_paths
|
||||||
|
end
|
||||||
|
|
||||||
def self.all_icons(theme_ids = [])
|
def self.all_icons(theme_ids = [])
|
||||||
get_set_cache("icons_#{Theme.transform_ids(theme_ids).join(',')}") do
|
get_set_cache("icons_#{Theme.transform_ids(theme_ids).join(',')}") do
|
||||||
|
@ -200,6 +220,7 @@ module SvgSprite
|
||||||
.merge(badge_icons)
|
.merge(badge_icons)
|
||||||
.merge(group_icons)
|
.merge(group_icons)
|
||||||
.merge(theme_icons(theme_ids))
|
.merge(theme_icons(theme_ids))
|
||||||
|
.merge(custom_icons(theme_ids))
|
||||||
.delete_if { |i| i.blank? || i.include?("/") }
|
.delete_if { |i| i.blank? || i.include?("/") }
|
||||||
.map! { |i| process(i.dup) }
|
.map! { |i| process(i.dup) }
|
||||||
.merge(SVG_ICONS)
|
.merge(SVG_ICONS)
|
||||||
|
@ -221,11 +242,13 @@ module SvgSprite
|
||||||
cache&.clear
|
cache&.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.sprite_sources(theme_ids)
|
||||||
|
CORE_SVG_SPRITES | custom_svg_sprites(theme_ids)
|
||||||
|
end
|
||||||
|
|
||||||
def self.bundle(theme_ids = [])
|
def self.bundle(theme_ids = [])
|
||||||
icons = all_icons(theme_ids)
|
icons = all_icons(theme_ids)
|
||||||
|
|
||||||
doc = File.open("#{Rails.root}/vendor/assets/svg-icons/fontawesome/solid.svg") { |f| Nokogiri::XML(f) }
|
|
||||||
|
|
||||||
svg_subset = """<!--
|
svg_subset = """<!--
|
||||||
Discourse SVG subset of Font Awesome Free by @fontawesome - https://fontawesome.com
|
Discourse SVG subset of Font Awesome Free by @fontawesome - https://fontawesome.com
|
||||||
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
@ -233,7 +256,7 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' style='display: none;'>
|
<svg xmlns='http://www.w3.org/2000/svg' style='display: none;'>
|
||||||
""".dup
|
""".dup
|
||||||
|
|
||||||
SVG_SPRITE_PATHS.each do |fname|
|
sprite_sources(theme_ids).each do |fname|
|
||||||
svg_file = Nokogiri::XML(File.open(fname)) do |config|
|
svg_file = Nokogiri::XML(File.open(fname)) do |config|
|
||||||
config.options = Nokogiri::XML::ParseOptions::NOBLANKS
|
config.options = Nokogiri::XML::ParseOptions::NOBLANKS
|
||||||
end
|
end
|
||||||
|
@ -256,7 +279,7 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
||||||
def self.search(searched_icon)
|
def self.search(searched_icon)
|
||||||
searched_icon = process(searched_icon.dup)
|
searched_icon = process(searched_icon.dup)
|
||||||
|
|
||||||
SVG_SPRITE_PATHS.each do |fname|
|
sprite_sources([SiteSetting.default_theme_id]).each do |fname|
|
||||||
svg_file = Nokogiri::XML(File.open(fname))
|
svg_file = Nokogiri::XML(File.open(fname))
|
||||||
svg_filename = "#{File.basename(fname, ".svg")}"
|
svg_filename = "#{File.basename(fname, ".svg")}"
|
||||||
|
|
||||||
|
@ -274,6 +297,10 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.theme_sprite_variable_name
|
||||||
|
THEME_SPRITE_VAR_NAME
|
||||||
|
end
|
||||||
|
|
||||||
def self.prepare_symbol(symbol, svg_filename)
|
def self.prepare_symbol(symbol, svg_filename)
|
||||||
icon_id = symbol.attr('id')
|
icon_id = symbol.attr('id')
|
||||||
|
|
||||||
|
@ -331,6 +358,19 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
||||||
theme_icon_settings
|
theme_icon_settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.custom_icons(theme_ids)
|
||||||
|
# Automatically register icons in sprites added via themes or plugins
|
||||||
|
icons = []
|
||||||
|
custom_svg_sprites(theme_ids).each do |fname|
|
||||||
|
svg_file = Nokogiri::XML(File.open(fname))
|
||||||
|
|
||||||
|
svg_file.css('symbol').each do |sym|
|
||||||
|
icons << sym.attributes['id'].value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
icons
|
||||||
|
end
|
||||||
|
|
||||||
def self.fa4_shim_file
|
def self.fa4_shim_file
|
||||||
"#{Rails.root}/lib/svg_sprite/fa4-renames.json"
|
"#{Rails.root}/lib/svg_sprite/fa4-renames.json"
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,6 +37,7 @@ class UploadCreator
|
||||||
|
|
||||||
is_image = FileHelper.is_supported_image?(@filename)
|
is_image = FileHelper.is_supported_image?(@filename)
|
||||||
is_image ||= @image_info && FileHelper.is_supported_image?("test.#{@image_info.type}")
|
is_image ||= @image_info && FileHelper.is_supported_image?("test.#{@image_info.type}")
|
||||||
|
is_image = false if @opts[:for_theme]
|
||||||
|
|
||||||
if is_image
|
if is_image
|
||||||
extract_image_info!
|
extract_image_info!
|
||||||
|
|
|
@ -99,6 +99,19 @@ describe SvgSprite do
|
||||||
expect(SvgSprite.all_icons([parent_theme.id])).to include("dragon")
|
expect(SvgSprite.all_icons([parent_theme.id])).to include("dragon")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes custom icons from a sprite in a theme' do
|
||||||
|
theme = Fabricate(:theme)
|
||||||
|
fname = "custom-theme-icon-sprite.svg"
|
||||||
|
|
||||||
|
upload = UploadCreator.new(file_from_fixtures(fname), fname, for_theme: true).create_for(-1)
|
||||||
|
|
||||||
|
theme.set_field(target: :common, name: SvgSprite.theme_sprite_variable_name, upload_id: upload.id, type: :theme_upload_var)
|
||||||
|
theme.save!
|
||||||
|
|
||||||
|
expect(Upload.where(id: upload.id)).to be_exist
|
||||||
|
expect(SvgSprite.bundle([theme.id])).to match(/my-custom-theme-icon/)
|
||||||
|
end
|
||||||
|
|
||||||
it 'includes icons from SiteSettings' do
|
it 'includes icons from SiteSettings' do
|
||||||
SiteSetting.svg_icon_subset = "blender|drafting-compass|fab-bandcamp"
|
SiteSetting.svg_icon_subset = "blender|drafting-compass|fab-bandcamp"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||||
|
<symbol id="my-custom-theme-icon" viewBox="0 0 496 512">
|
||||||
|
<path d="M248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 376c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32zm0-128c-53.02 0-96 42.98-96 96s42.98 96 96 96c-106.04 0-192-85.96-192-192S141.96 64 248 64c53.02 0 96 42.98 96 96s-42.98 96-96 96zm0-128c-17.67 0-32 14.33-32 32s14.33 32 32 32 32-14.33 32-32-14.33-32-32-32z"></path>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 570 B |
|
@ -47,5 +47,23 @@ describe SvgSpriteController do
|
||||||
get "/svg-sprite/search/fa-not-a-valid-icon"
|
get "/svg-sprite/search/fa-not-a-valid-icon"
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should find a custom icon in default theme" do
|
||||||
|
theme = Fabricate(:theme)
|
||||||
|
fname = "custom-theme-icon-sprite.svg"
|
||||||
|
|
||||||
|
upload = UploadCreator.new(file_from_fixtures(fname), fname, for_theme: true).create_for(-1)
|
||||||
|
|
||||||
|
theme.set_field(target: :common, name: SvgSprite.theme_sprite_variable_name, upload_id: upload.id, type: :theme_upload_var)
|
||||||
|
theme.save!
|
||||||
|
|
||||||
|
SiteSetting.default_theme_id = theme.id
|
||||||
|
|
||||||
|
user = sign_in(Fabricate(:user))
|
||||||
|
|
||||||
|
get "/svg-sprite/search/fa-my-custom-theme-icon"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to include('my-custom-theme-icon')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue