Files
discourse/lib/svg_sprite.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

509 lines
11 KiB
Ruby
Raw Normal View History

2018-11-26 16:49:57 -05:00
# frozen_string_literal: true
module SvgSprite
SVG_ICONS ||=
Set.new(
%w[
adjust
2020-01-27 10:29:43 -03:00
address-book
2023-02-08 11:21:39 -08:00
align-left
2018-12-07 13:00:02 +01:00
ambulance
2018-11-26 16:49:57 -05:00
anchor
angle-double-down
angle-double-up
angle-double-right
angle-double-left
2018-11-26 16:49:57 -05:00
angle-down
angle-right
angle-up
archive
arrow-down
arrow-left
arrow-up
arrows-alt-h
arrows-alt-v
2018-11-26 16:49:57 -05:00
at
2019-08-29 12:03:43 -03:00
asterisk
2018-11-26 16:49:57 -05:00
backward
ban
bars
bed
bell
2018-11-26 16:49:57 -05:00
bell-slash
bold
book
book-reader
2018-11-26 16:49:57 -05:00
bookmark
2023-08-09 10:51:09 +02:00
bookmark-delete
2018-11-26 16:49:57 -05:00
briefcase
2023-02-08 11:21:39 -08:00
bullseye
2018-11-26 16:49:57 -05:00
calendar-alt
caret-down
caret-left
caret-right
caret-up
certificate
chart-bar
chart-pie
check
check-circle
check-square
2023-02-08 11:21:39 -08:00
chevron-circle-down
2018-11-26 16:49:57 -05:00
chevron-down
chevron-left
2018-11-26 16:49:57 -05:00
chevron-right
chevron-up
circle
2023-02-08 11:21:39 -08:00
cloud-upload-alt
2018-11-26 16:49:57 -05:00
code
cog
columns
comment
compress
copy
crosshairs
cube
desktop
discourse-amazon
discourse-bell-exclamation
discourse-bell-one
discourse-bell-slash
discourse-bookmark-clock
2018-12-03 22:29:20 -05:00
discourse-compress
2021-06-23 11:12:48 -04:00
discourse-emojis
2018-12-03 22:29:20 -05:00
discourse-expand
discourse-other-tab
discourse-threads
2018-11-26 16:49:57 -05:00
download
ellipsis-h
ellipsis-v
envelope
envelope-square
exchange-alt
exclamation-circle
exclamation-triangle
external-link-alt
eye
2018-12-07 15:49:18 +00:00
fab-android
fab-apple
2019-05-30 16:29:43 +03:00
fab-chrome
2019-08-30 10:54:19 +01:00
fab-discord
fab-discourse
2018-11-26 16:49:57 -05:00
fab-facebook-square
2019-02-27 09:52:06 -05:00
fab-facebook
2018-11-26 16:49:57 -05:00
fab-github
fab-instagram
fab-linux
fab-twitter
2018-11-26 16:49:57 -05:00
fab-twitter-square
fab-wikipedia-w
2018-11-26 16:49:57 -05:00
fab-windows
far-bell
far-bell-slash
far-calendar-plus
far-chart-bar
far-check-square
far-circle
far-clipboard
far-clock
far-comment
far-comments
2019-01-10 14:26:53 -05:00
far-copyright
2018-11-26 16:49:57 -05:00
far-dot-circle
far-edit
far-envelope
far-eye
far-eye-slash
far-file-alt
far-frown
far-heart
far-image
far-list-alt
2020-07-16 16:26:17 -07:00
far-meh
2018-11-26 16:49:57 -05:00
far-moon
far-smile
far-square
far-star
2018-11-26 16:49:57 -05:00
far-sun
far-thumbs-down
far-thumbs-up
far-trash-alt
fast-backward
fast-forward
file
file-alt
filter
flag
folder
folder-open
forward
gavel
gift
2018-11-26 16:49:57 -05:00
globe
globe-americas
2023-02-08 11:21:39 -08:00
grip-lines
2018-11-26 16:49:57 -05:00
hand-point-right
hands-helping
2018-11-26 16:49:57 -05:00
heart
history
2018-11-26 16:49:57 -05:00
home
2020-10-30 11:22:56 -03:00
hourglass-start
id-card
image
images
inbox
2018-11-26 16:49:57 -05:00
info-circle
italic
key
keyboard
layer-group
2018-11-26 16:49:57 -05:00
link
list
list-ol
list-ul
lock
magic
map-marker-alt
2018-11-26 16:49:57 -05:00
microphone-slash
minus
minus-circle
mobile-alt
2020-12-18 09:03:51 -06:00
moon
2018-11-26 16:49:57 -05:00
paint-brush
paper-plane
pause
2018-11-26 16:49:57 -05:00
pencil-alt
2018-12-07 13:00:02 +01:00
play
2018-11-26 16:49:57 -05:00
plug
plus
plus-circle
plus-square
power-off
puzzle-piece
2018-11-26 16:49:57 -05:00
question
question-circle
quote-left
quote-right
random
redo
reply
rocket
search
search-plus
search-minus
2018-11-26 16:49:57 -05:00
share
shield-alt
sign-in-alt
2018-11-26 16:49:57 -05:00
sign-out-alt
signal
2021-10-06 11:42:52 -04:00
sliders-h
square-full
star
2018-11-26 16:49:57 -05:00
step-backward
step-forward
stream
sync-alt
2018-11-26 16:49:57 -05:00
sync
table
tag
tags
2018-11-26 16:49:57 -05:00
tasks
th
2018-11-26 16:49:57 -05:00
thermometer-three-quarters
thumbs-down
thumbs-up
thumbtack
times
times-circle
toggle-off
toggle-on
2018-11-26 16:49:57 -05:00
trash-alt
undo
unlink
unlock
unlock-alt
upload
user
user-cog
user-edit
user-friends
2018-11-26 16:49:57 -05:00
user-plus
user-secret
user-shield
2018-11-26 16:49:57 -05:00
user-times
users
wrench
2022-05-02 17:10:26 +02:00
spinner
tippy-rounded-arrow
2018-11-26 16:49:57 -05:00
],
)
2023-01-09 12:10:19 +00:00
2018-11-26 16:49:57 -05:00
FA_ICON_MAP = { "far fa-" => "far-", "fab fa-" => "fab-", "fas fa-" => "", "fa-" => "" }
CORE_SVG_SPRITES = Dir.glob("#{Rails.root}/vendor/assets/svg-icons/**/*.svg")
THEME_SPRITE_VAR_NAME = "icons-sprite"
def self.preload
settings_icons
group_icons
badge_icons
end
2023-03-20 11:41:23 -05:00
def self.symbols_for(svg_filename, sprite, strict:)
if strict
Nokogiri.XML(sprite) { |config| config.options = Nokogiri::XML::ParseOptions::NOBLANKS }
else
Nokogiri.XML(sprite)
end.css("symbol")
.filter_map do |sym|
icon_id = prepare_symbol(sym, svg_filename)
if icon_id.present?
sym.attributes["id"].value = icon_id
sym.css("title").each(&:remove)
[icon_id, sym.to_xml]
2023-03-14 13:11:45 -05:00
end
end
2023-03-20 11:41:23 -05:00
.to_h
end
def self.core_svgs
@core_svgs ||=
CORE_SVG_SPRITES.reduce({}) do |symbols, path|
symbols.merge!(symbols_for(File.basename(path, ".svg"), File.read(path), strict: true))
end
2023-03-14 13:11:45 -05:00
end
# Just used in tests
def self.clear_plugin_svg_sprite_cache!
2023-03-20 11:41:23 -05:00
@plugin_svgs = nil
2023-03-14 13:11:45 -05:00
end
2023-03-20 11:41:23 -05:00
def self.plugin_svgs
@plugin_svgs ||=
2023-03-14 13:11:45 -05:00
begin
plugin_paths = []
Discourse
.plugins
.map { |plugin| File.dirname(plugin.path) }
.each { |path| plugin_paths << "#{path}/svg-icons/*.svg" }
custom_sprite_paths = Dir.glob(plugin_paths)
2023-03-20 11:41:23 -05:00
custom_sprite_paths.reduce({}) do |symbols, path|
symbols.merge!(symbols_for(File.basename(path, ".svg"), File.read(path), strict: true))
end
2023-03-14 13:11:45 -05:00
end
end
2023-03-20 11:41:23 -05:00
def self.theme_svgs(theme_id)
2023-03-14 13:11:45 -05:00
if theme_id.present?
theme_ids = Theme.transform_ids(theme_id)
2023-03-14 13:11:45 -05:00
get_set_cache("theme_svg_sprites_#{theme_ids.join(",")}") do
theme_field_uploads =
ThemeField.where(
type_id: ThemeField.types[:theme_upload_var],
name: THEME_SPRITE_VAR_NAME,
2023-03-14 13:11:45 -05:00
theme_id: theme_ids,
).pluck(:upload_id)
theme_sprites = ThemeSvgSprite.where(theme_id: theme_ids).pluck(:upload_id, :sprite)
missing_sprites = (theme_field_uploads - theme_sprites.map(&:first))
if missing_sprites.present?
Rails.logger.warn(
"Missing ThemeSvgSprites for theme #{theme_id}, uploads #{missing_sprites.join(", ")}",
)
2023-03-14 13:11:45 -05:00
end
2023-03-20 11:41:23 -05:00
theme_sprites.reduce({}) do |symbols, (upload_id, sprite)|
begin
symbols.merge!(symbols_for("theme_#{theme_id}_#{upload_id}.svg", sprite, strict: false))
rescue => e
Rails.logger.warn(
"Bad XML in custom sprite in theme with ID=#{theme_id}. Error info: #{e.inspect}",
)
end
symbols
2023-03-14 13:11:45 -05:00
end
end
2023-03-14 13:11:45 -05:00
else
2023-03-20 11:41:23 -05:00
{}
end
end
2019-01-12 09:59:12 -05:00
2023-03-20 11:41:23 -05:00
def self.custom_svgs(theme_id)
plugin_svgs.merge(theme_svgs(theme_id))
2023-03-14 13:11:45 -05:00
end
def self.all_icons(theme_id = nil)
get_set_cache("icons_#{Theme.transform_ids(theme_id).join(",")}") do
Set
.new()
.merge(settings_icons)
.merge(plugin_icons)
.merge(badge_icons)
.merge(group_icons)
.merge(theme_icons(theme_id))
.merge(custom_icons(theme_id))
.delete_if { |i| i.blank? || i.include?("/") }
.map! { |i| process(i.dup) }
.merge(SVG_ICONS)
.sort
end
2018-11-26 16:49:57 -05:00
end
def self.version(theme_id = nil)
get_set_cache("version_#{Theme.transform_ids(theme_id).join(",")}") do
Digest::SHA1.hexdigest(bundle(theme_id))
end
2018-11-26 16:49:57 -05:00
end
def self.path(theme_id = nil)
"/svg-sprite/#{Discourse.current_hostname}/svg-#{theme_id}-#{version(theme_id)}.js"
2018-11-26 16:49:57 -05:00
end
def self.expire_cache
cache&.clear
2018-11-26 16:49:57 -05:00
end
2023-03-20 11:41:23 -05:00
def self.svgs_for(theme_id)
svgs = core_svgs
svgs = svgs.merge(custom_svgs(theme_id)) if theme_id.present?
svgs
end
def self.bundle(theme_id = nil)
icons = all_icons(theme_id)
2018-11-26 16:49:57 -05:00
svg_subset =
"" \
"<!--
2019-03-06 09:21:31 -05:00
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)
2018-11-26 16:49:57 -05:00
-->
<svg xmlns='http://www.w3.org/2000/svg' style='display: none;'>
2023-01-09 12:10:19 +00:00
" \
2018-11-26 16:49:57 -05:00
"".dup
2023-03-20 11:41:23 -05:00
svg_subset << core_svgs.slice(*icons).values.join
svg_subset << custom_svgs(theme_id).values.join
2018-11-26 16:49:57 -05:00
svg_subset << "</svg>"
end
def self.search(searched_icon)
searched_icon = process(searched_icon.dup)
2023-03-20 11:41:23 -05:00
svgs_for(SiteSetting.default_theme_id)[searched_icon] || false
2018-11-26 16:49:57 -05:00
end
def self.icon_picker_search(keyword, only_available = false)
icons = all_icons(SiteSetting.default_theme_id) if only_available
2023-03-20 11:41:23 -05:00
symbols = svgs_for(SiteSetting.default_theme_id)
symbols.slice!(*icons) if only_available
symbols.reject! { |icon_id, sym| !icon_id.include?(keyword) } unless keyword.empty?
symbols.sort_by(&:first).map { |icon_id, symbol| { id: icon_id, symbol: symbol } }
end
# For use in no_ember .html.erb layouts
def self.raw_svg(name)
get_set_cache("raw_svg_#{name}") do
symbol = search(name)
break "" unless symbol
symbol = Nokogiri.XML(symbol).children.first
symbol.name = "svg"
<<~HTML
<svg class="fa d-icon svg-icon svg-node" aria-hidden="true">#{symbol}</svg>
HTML
end.html_safe
end
def self.theme_sprite_variable_name
THEME_SPRITE_VAR_NAME
end
def self.prepare_symbol(symbol, svg_filename = nil)
2018-11-26 16:49:57 -05:00
icon_id = symbol.attr("id")
case svg_filename
when "regular"
icon_id = icon_id.prepend("far-")
when "brands"
icon_id = icon_id.prepend("fab-")
end
icon_id
end
def self.settings_icons
get_set_cache("settings_icons") do
# includes svg_icon_subset and any settings containing _icon (incl. plugin settings)
site_setting_icons = []
2018-11-26 16:49:57 -05:00
SiteSetting.settings_hash.select do |key, value|
site_setting_icons |= value.split("|") if key.to_s.include?("_icon") && String === value
2018-11-26 16:49:57 -05:00
end
site_setting_icons
end
2018-11-26 16:49:57 -05:00
end
def self.plugin_icons
DiscoursePluginRegistry.svg_icons
end
def self.badge_icons
get_set_cache("badge_icons") { Badge.pluck(:icon).uniq }
2018-11-26 16:49:57 -05:00
end
def self.group_icons
get_set_cache("group_icons") { Group.pluck(:flair_icon).uniq }
2018-11-26 16:49:57 -05:00
end
def self.theme_icons(theme_id)
return [] if theme_id.blank?
2018-11-26 16:49:57 -05:00
theme_icon_settings = []
2021-06-18 10:16:26 +08:00
theme_ids = Theme.transform_ids(theme_id)
2018-11-26 16:49:57 -05:00
# Need to load full records for default values
2021-06-18 10:16:26 +08:00
Theme
.where(id: theme_ids)
.each do |theme|
_settings =
theme.cached_settings.each do |key, value|
2018-11-29 19:11:32 -05:00
if key.to_s.include?("_icon") && String === value
2018-11-26 16:49:57 -05:00
theme_icon_settings |= value.split("|")
end
2023-01-09 12:10:19 +00:00
end
2018-11-26 16:49:57 -05:00
end
2021-06-18 10:16:26 +08:00
theme_icon_settings |= ThemeModifierHelper.new(theme_ids: theme_ids).svg_icons
2018-11-26 16:49:57 -05:00
theme_icon_settings
end
def self.custom_icons(theme_id)
# Automatically register icons in sprites added via themes or plugins
2023-03-20 11:41:23 -05:00
custom_svgs(theme_id).keys
end
2018-11-26 16:49:57 -05:00
def self.process(icon_name)
icon_name = icon_name.strip
FA_ICON_MAP.each { |k, v| icon_name = icon_name.sub(k, v) }
icon_name
2018-11-26 16:49:57 -05:00
end
def self.get_set_cache(key, &block)
cache.defer_get_set(key, &block)
end
def self.cache
@cache ||= DistributedCache.new("svg_sprite")
end
2018-11-26 16:49:57 -05:00
end