FEATURE: Add endpoint for individual SVG icons (#9765)

FEATURE: Add endpoint for individual SVG icons (#9765)

diff --git a/app/controllers/svg_sprite_controller.rb b/app/controllers/svg_sprite_controller.rb
index c4cd07e..1a7aad9 100644
--- a/app/controllers/svg_sprite_controller.rb
+++ b/app/controllers/svg_sprite_controller.rb
@@ -1,9 +1,9 @@
 # frozen_string_literal: true
 
 class SvgSpriteController < ApplicationController
-  skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search]
+  skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search, :svg_icon]
 
-  requires_login except: [:show]
+  requires_login except: [:show, :svg_icon]
 
   def show
 
@@ -49,4 +49,29 @@ class SvgSpriteController < ApplicationController
       render json: icons.take(200), root: false
     end
   end
+
+  def svg_icon
+    no_cookies
+
+    RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
+      params.permit(:color)
+      name = params.require(:name)
+      icon = SvgSprite.search(name)
+
+      if icon.blank?
+        render body: nil, status: 404
+      else
+        doc = Nokogiri.XML(icon)
+        doc.at_xpath("symbol").name = "svg"
+        doc.at_xpath("svg")['xmlns'] = "http://www.w3.org/2000/svg"
+        doc.at_xpath("svg")['fill'] = "##{params[:color]}" if params[:color]
+
+        response.headers["Last-Modified"] = 1.years.ago.httpdate
+        response.headers["Content-Length"] = doc.to_s.bytesize.to_s
+        immutable_for 1.day
+
+        render plain: doc, disposition: nil, content_type: 'image/svg+xml'
+      end
+    end
+  end
 end
diff --git a/config/routes.rb b/config/routes.rb
index 10199d1..353af09 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -503,6 +503,7 @@ Discourse::Application.routes.draw do
   get "svg-sprite/:hostname/svg-:theme_ids-:version.js" => "svg_sprite#show", constraints: { hostname: /[\w\.-]+/, version: /\h{40}/, theme_ids: /([0-9]+(,[0-9]+)*)?/, format: :js }
   get "svg-sprite/search/:keyword" => "svg_sprite#search", format: false, constraints: { keyword: /[-a-z0-9\s\%]+/ }
   get "svg-sprite/picker-search" => "svg_sprite#icon_picker_search", defaults: { format: :json }
+  get "svg-sprite/:hostname/icon(/:color)/:name.svg" => "svg_sprite#svg_icon", constraints: { hostname: /[\w\.-]+/, name: /[-a-z0-9\s\%]+/, color: /(\h{6})/, format: :svg }
 
   get "highlight-js/:hostname/:version.js" => "highlight_js#show", constraints: { hostname: /[\w\.-]+/, format: :js }
 
diff --git a/spec/requests/svg_sprite_controller_spec.rb b/spec/requests/svg_sprite_controller_spec.rb
index cdfdab7..5402355 100644
--- a/spec/requests/svg_sprite_controller_spec.rb
+++ b/spec/requests/svg_sprite_controller_spec.rb
@@ -93,4 +93,32 @@ describe SvgSpriteController do
       expect(data[0]["id"]).to eq("fab-500px")
     end
   end
+
+  context 'svg_icon' do
+    it "requires .svg extension" do
+      get "/svg-sprite/#{Discourse.current_hostname}/icon/bolt"
+      expect(response.status).to eq(404)
+    end
+
+    it "returns SVG given an icon name" do
+      get "/svg-sprite/#{Discourse.current_hostname}/icon/bolt.svg"
+      expect(response.status).to eq(200)
+      expect(response.body).to include('bolt')
+    end
+
+    it "returns SVG given an icon name and a color" do
+      get "/svg-sprite/#{Discourse.current_hostname}/icon/CC0000/fab-github.svg"
+      expect(response.status).to eq(200)
+
+      expect(response.body).to include('fab-github')
+      expect(response.body).to include('fill="#CC0000"')
+      expect(response.headers["Cache-Control"]).to eq("max-age=86400, public, immutable")
+    end
+
+    it "ignores non-HEX colors" do
+      get "/svg-sprite/#{Discourse.current_hostname}/icon/orange/fab-github.svg"
+      expect(response.status).to eq(404)
+    end
+
+  end
 end

GitHub sha: aee8e62e

1 Like

This commit appears in #9765 which was merged by pmusaraj.