PERF: Eager load Theme associations in Stylesheet Manager.
Before this change, calling `StyleSheet::Manager.stylesheet_details` for the first time resulted in multiple queries to the database. This is because the code was modelled in a way where each `Theme` was loaded from the database one at a time. This PR restructures the code such that it allows us to load all the theme records in a single query. It also allows us to eager load the required associations upfront. In order to achieve this, I removed the support of loading multiple themes per request. It was initially added to support user selectable theme components but the feature was never completed and abandoned because it wasn't a feature that we thought was worth building.
This commit is contained in:
parent
53dab8cf1e
commit
8e3691d537
|
@ -10,7 +10,7 @@ class ApplicationController < ActionController::Base
|
|||
include Hijack
|
||||
include ReadOnlyHeader
|
||||
|
||||
attr_reader :theme_ids
|
||||
attr_reader :theme_id
|
||||
|
||||
serialization_scope :guardian
|
||||
|
||||
|
@ -448,35 +448,34 @@ class ApplicationController < ActionController::Base
|
|||
resolve_safe_mode
|
||||
return if request.env[NO_CUSTOM]
|
||||
|
||||
theme_ids = []
|
||||
theme_id = nil
|
||||
|
||||
if preview_theme_id = request[:preview_theme_id]&.to_i
|
||||
ids = [preview_theme_id]
|
||||
theme_ids = ids if guardian.allow_themes?(ids, include_preview: true)
|
||||
if (preview_theme_id = request[:preview_theme_id]&.to_i) &&
|
||||
guardian.allow_themes?([preview_theme_id], include_preview: true)
|
||||
|
||||
theme_id = preview_theme_id
|
||||
end
|
||||
|
||||
user_option = current_user&.user_option
|
||||
|
||||
if theme_ids.blank?
|
||||
if theme_id.blank?
|
||||
ids, seq = cookies[:theme_ids]&.split("|")
|
||||
ids = ids&.split(",")&.map(&:to_i)
|
||||
if ids.present? && seq && seq.to_i == user_option&.theme_key_seq.to_i
|
||||
theme_ids = ids if guardian.allow_themes?(ids)
|
||||
id = ids&.split(",")&.map(&:to_i)&.first
|
||||
if id.present? && seq && seq.to_i == user_option&.theme_key_seq.to_i
|
||||
theme_id = id if guardian.allow_themes?([id])
|
||||
end
|
||||
end
|
||||
|
||||
if theme_ids.blank?
|
||||
if theme_id.blank?
|
||||
ids = user_option&.theme_ids || []
|
||||
theme_ids = ids if guardian.allow_themes?(ids)
|
||||
theme_id = ids.first if guardian.allow_themes?(ids)
|
||||
end
|
||||
|
||||
if theme_ids.blank? && SiteSetting.default_theme_id != -1
|
||||
if guardian.allow_themes?([SiteSetting.default_theme_id])
|
||||
theme_ids << SiteSetting.default_theme_id
|
||||
end
|
||||
if theme_id.blank? && SiteSetting.default_theme_id != -1 && guardian.allow_themes?([SiteSetting.default_theme_id])
|
||||
theme_id = SiteSetting.default_theme_id
|
||||
end
|
||||
|
||||
@theme_ids = request.env[:resolved_theme_ids] = theme_ids
|
||||
@theme_id = request.env[:resolved_theme_id] = theme_id
|
||||
end
|
||||
|
||||
def guardian
|
||||
|
@ -635,10 +634,10 @@ class ApplicationController < ActionController::Base
|
|||
target = view_context.mobile_view? ? :mobile : :desktop
|
||||
|
||||
data =
|
||||
if @theme_ids.present?
|
||||
if @theme_id.present?
|
||||
{
|
||||
top: Theme.lookup_field(@theme_ids, target, "after_header"),
|
||||
footer: Theme.lookup_field(@theme_ids, target, "footer")
|
||||
top: Theme.lookup_field(@theme_id, target, "after_header"),
|
||||
footer: Theme.lookup_field(@theme_id, target, "footer")
|
||||
}
|
||||
else
|
||||
{}
|
||||
|
@ -943,9 +942,9 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def activated_themes_json
|
||||
ids = @theme_ids&.compact
|
||||
return "{}" if ids.blank?
|
||||
ids = Theme.transform_ids(ids)
|
||||
id = @theme_id
|
||||
return "{}" if id.blank?
|
||||
ids = Theme.transform_ids(id)
|
||||
Theme.where(id: ids).pluck(:id, :name).to_h.to_json
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ class BootstrapController < ApplicationController
|
|||
).each do |file|
|
||||
add_style(file, plugin: true)
|
||||
end
|
||||
add_style(mobile_view? ? :mobile_theme : :desktop_theme) if theme_ids.present?
|
||||
add_style(mobile_view? ? :mobile_theme : :desktop_theme) if theme_id.present?
|
||||
|
||||
extra_locales = []
|
||||
if ExtraLocalesController.client_overrides_exist?
|
||||
|
@ -51,7 +51,7 @@ class BootstrapController < ApplicationController
|
|||
).map { |f| script_asset_path(f) }
|
||||
|
||||
bootstrap = {
|
||||
theme_ids: theme_ids,
|
||||
theme_ids: [theme_id],
|
||||
title: SiteSetting.title,
|
||||
current_homepage: current_homepage,
|
||||
locale_script: locale,
|
||||
|
@ -75,15 +75,14 @@ class BootstrapController < ApplicationController
|
|||
private
|
||||
def add_scheme(scheme_id, media)
|
||||
return if scheme_id.to_i == -1
|
||||
theme_id = theme_ids&.first
|
||||
|
||||
if style = Stylesheet::Manager.color_scheme_stylesheet_details(scheme_id, media, theme_id)
|
||||
if style = Stylesheet::Manager.new(theme_id: theme_id).color_scheme_stylesheet_details(scheme_id, media)
|
||||
@stylesheets << { href: style[:new_href], media: media }
|
||||
end
|
||||
end
|
||||
|
||||
def add_style(target, opts = nil)
|
||||
if styles = Stylesheet::Manager.stylesheet_details(target, 'all', theme_ids)
|
||||
if styles = Stylesheet::Manager.new(theme_id: theme_id).stylesheet_details(target, 'all')
|
||||
styles.each do |style|
|
||||
@stylesheets << {
|
||||
href: style[:new_href],
|
||||
|
@ -117,11 +116,11 @@ private
|
|||
|
||||
theme_view = mobile_view? ? :mobile : :desktop
|
||||
|
||||
add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_ids, theme_view, 'body_tag'))
|
||||
add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_ids, theme_view, 'head_tag'))
|
||||
add_if_present(theme_html, :header, Theme.lookup_field(theme_ids, theme_view, 'header'))
|
||||
add_if_present(theme_html, :translations, Theme.lookup_field(theme_ids, :translations, I18n.locale))
|
||||
add_if_present(theme_html, :js, Theme.lookup_field(theme_ids, :extra_js, nil))
|
||||
add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_id, theme_view, 'body_tag'))
|
||||
add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_id, theme_view, 'head_tag'))
|
||||
add_if_present(theme_html, :header, Theme.lookup_field(theme_id, theme_view, 'header'))
|
||||
add_if_present(theme_html, :translations, Theme.lookup_field(theme_id, :translations, I18n.locale))
|
||||
add_if_present(theme_html, :js, Theme.lookup_field(theme_id, :extra_js, nil))
|
||||
|
||||
theme_html
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ class QunitController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
request.env[:resolved_theme_ids] = [theme.id]
|
||||
request.env[:resolved_theme_id] = theme.id
|
||||
request.env[:skip_theme_ids_transformation] = true
|
||||
end
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ class StylesheetsController < ApplicationController
|
|||
params.require("id")
|
||||
params.permit("theme_id")
|
||||
|
||||
stylesheet = Stylesheet::Manager.color_scheme_stylesheet_details(params[:id], 'all', params[:theme_id])
|
||||
manager = Stylesheet::Manager.new(theme_id: params[:theme_id])
|
||||
stylesheet = manager.color_scheme_stylesheet_details(params[:id], 'all')
|
||||
render json: stylesheet
|
||||
end
|
||||
protected
|
||||
|
@ -40,16 +41,19 @@ class StylesheetsController < ApplicationController
|
|||
# we hold off re-compilation till someone asks for asset
|
||||
if target.include?("color_definitions")
|
||||
split_target, color_scheme_id = target.split(/_(-?[0-9]+)/)
|
||||
Stylesheet::Manager.color_scheme_stylesheet_link_tag(color_scheme_id)
|
||||
|
||||
Stylesheet::Manager.new.color_scheme_stylesheet_link_tag(color_scheme_id)
|
||||
else
|
||||
if target.include?("theme")
|
||||
split_target, theme_id = target.split(/_(-?[0-9]+)/)
|
||||
theme = Theme.find_by(id: theme_id) if theme_id.present?
|
||||
else
|
||||
split_target, color_scheme_id = target.split(/_(-?[0-9]+)/)
|
||||
theme = Theme.find_by(color_scheme_id: color_scheme_id)
|
||||
end
|
||||
Stylesheet::Manager.stylesheet_link_tag(split_target, nil, theme&.id)
|
||||
theme_id =
|
||||
if target.include?("theme")
|
||||
split_target, theme_id = target.split(/_(-?[0-9]+)/)
|
||||
Theme.where(id: theme_id).pluck_first(:id) if theme_id.present?
|
||||
else
|
||||
split_target, color_scheme_id = target.split(/_(-?[0-9]+)/)
|
||||
Theme.where(color_scheme_id: color_scheme_id).pluck_first(:id)
|
||||
end
|
||||
|
||||
Stylesheet::Manager.new(theme_id: theme_id).stylesheet_link_tag(split_target, nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ class SvgSpriteController < ApplicationController
|
|||
no_cookies
|
||||
|
||||
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
|
||||
theme_ids = params[:theme_ids].split(",").map(&:to_i)
|
||||
theme_id = params[:theme_id].to_i
|
||||
|
||||
if SvgSprite.version(theme_ids) != params[:version]
|
||||
return redirect_to path(SvgSprite.path(theme_ids))
|
||||
if SvgSprite.version(theme_id) != params[:version]
|
||||
return redirect_to path(SvgSprite.path(theme_id))
|
||||
end
|
||||
|
||||
svg_sprite = "window.__svg_sprite = #{SvgSprite.bundle(theme_ids).inspect};"
|
||||
svg_sprite = "window.__svg_sprite = #{SvgSprite.bundle(theme_id).inspect};"
|
||||
|
||||
response.headers["Last-Modified"] = 10.years.ago.httpdate
|
||||
response.headers["Content-Length"] = svg_sprite.bytesize.to_s
|
||||
|
|
|
@ -408,14 +408,19 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def theme_ids
|
||||
def theme_id
|
||||
if customization_disabled?
|
||||
[nil]
|
||||
nil
|
||||
else
|
||||
request.env[:resolved_theme_ids]
|
||||
request.env[:resolved_theme_id]
|
||||
end
|
||||
end
|
||||
|
||||
def stylesheet_manager
|
||||
return @stylesheet_manager if defined?(@stylesheet_manager)
|
||||
@stylesheet_manager = Stylesheet::Manager.new(theme_id: theme_id)
|
||||
end
|
||||
|
||||
def scheme_id
|
||||
return @scheme_id if defined?(@scheme_id)
|
||||
|
||||
|
@ -424,12 +429,9 @@ module ApplicationHelper
|
|||
return custom_user_scheme_id
|
||||
end
|
||||
|
||||
return if theme_ids.blank?
|
||||
return if theme_id.blank?
|
||||
|
||||
@scheme_id = Theme
|
||||
.where(id: theme_ids.first)
|
||||
.pluck(:color_scheme_id)
|
||||
.first
|
||||
@scheme_id = Theme.where(id: theme_id).pluck_first(:color_scheme_id)
|
||||
end
|
||||
|
||||
def dark_scheme_id
|
||||
|
@ -457,7 +459,7 @@ module ApplicationHelper
|
|||
|
||||
def theme_lookup(name)
|
||||
Theme.lookup_field(
|
||||
theme_ids,
|
||||
theme_id,
|
||||
mobile_view? ? :mobile : :desktop,
|
||||
name,
|
||||
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||
|
@ -466,7 +468,7 @@ module ApplicationHelper
|
|||
|
||||
def theme_translations_lookup
|
||||
Theme.lookup_field(
|
||||
theme_ids,
|
||||
theme_id,
|
||||
:translations,
|
||||
I18n.locale,
|
||||
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||
|
@ -475,7 +477,7 @@ module ApplicationHelper
|
|||
|
||||
def theme_js_lookup
|
||||
Theme.lookup_field(
|
||||
theme_ids,
|
||||
theme_id,
|
||||
:extra_js,
|
||||
nil,
|
||||
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||
|
@ -483,22 +485,26 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def discourse_stylesheet_link_tag(name, opts = {})
|
||||
if opts.key?(:theme_ids)
|
||||
ids = opts[:theme_ids] unless customization_disabled?
|
||||
else
|
||||
ids = theme_ids
|
||||
end
|
||||
manager =
|
||||
if opts.key?(:theme_id)
|
||||
Stylesheet::Manager.new(
|
||||
theme_id: customization_disabled? ? nil : opts[:theme_id]
|
||||
)
|
||||
else
|
||||
stylesheet_manager
|
||||
end
|
||||
|
||||
Stylesheet::Manager.stylesheet_link_tag(name, 'all', ids)
|
||||
manager.stylesheet_link_tag(name, 'all')
|
||||
end
|
||||
|
||||
def discourse_color_scheme_stylesheets
|
||||
result = +""
|
||||
result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(scheme_id, 'all', theme_ids)
|
||||
result << stylesheet_manager.color_scheme_stylesheet_link_tag(scheme_id, 'all')
|
||||
|
||||
if dark_scheme_id != -1
|
||||
result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)', theme_ids)
|
||||
result << stylesheet_manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)')
|
||||
end
|
||||
|
||||
result.html_safe
|
||||
end
|
||||
|
||||
|
@ -525,7 +531,7 @@ module ApplicationHelper
|
|||
asset_version: Discourse.assets_digest,
|
||||
disable_custom_css: loading_admin?,
|
||||
highlight_js_path: HighlightJs.path,
|
||||
svg_sprite_path: SvgSprite.path(theme_ids),
|
||||
svg_sprite_path: SvgSprite.path(theme_id),
|
||||
enable_js_error_reporting: GlobalSetting.enable_js_error_reporting,
|
||||
color_scheme_is_dark: dark_color_scheme?,
|
||||
user_color_scheme_id: scheme_id,
|
||||
|
@ -533,7 +539,7 @@ module ApplicationHelper
|
|||
}
|
||||
|
||||
if Rails.env.development?
|
||||
setup_data[:svg_icon_list] = SvgSprite.all_icons(theme_ids)
|
||||
setup_data[:svg_icon_list] = SvgSprite.all_icons(theme_id)
|
||||
|
||||
if ENV['DEBUG_PRELOADED_APP_DATA']
|
||||
setup_data[:debug_preloaded_app_data] = true
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QunitHelper
|
||||
def theme_tests
|
||||
theme = Theme.find_by(id: request.env[:resolved_theme_ids]&.first)
|
||||
theme = Theme.find_by(id: request.env[:resolved_theme_id])
|
||||
return "" if theme.blank?
|
||||
|
||||
_, digest = theme.baked_js_tests_with_digest
|
||||
|
|
|
@ -320,6 +320,7 @@ class ColorScheme < ActiveRecord::Base
|
|||
end
|
||||
if theme_ids.present?
|
||||
Stylesheet::Manager.cache.clear
|
||||
|
||||
Theme.notify_theme_change(
|
||||
theme_ids,
|
||||
with_scheme: true,
|
||||
|
|
|
@ -29,6 +29,9 @@ class Theme < ActiveRecord::Base
|
|||
has_many :locale_fields, -> { filter_locale_fields(I18n.fallbacks[I18n.locale]) }, class_name: 'ThemeField'
|
||||
has_many :upload_fields, -> { where(type_id: ThemeField.types[:theme_upload_var]).preload(:upload) }, class_name: 'ThemeField'
|
||||
has_many :extra_scss_fields, -> { where(target_id: Theme.targets[:extra_scss]) }, class_name: 'ThemeField'
|
||||
has_many :yaml_theme_fields, -> { where("name = 'yaml' AND type_id = ?", ThemeField.types[:yaml]) }, class_name: 'ThemeField'
|
||||
has_many :var_theme_fields, -> { where("type_id IN (?)", ThemeField.theme_var_type_ids) }, class_name: 'ThemeField'
|
||||
has_many :builder_theme_fields, -> { where("name IN (?)", ThemeField.scss_fields) }, class_name: 'ThemeField'
|
||||
|
||||
validate :component_validations
|
||||
|
||||
|
@ -164,6 +167,16 @@ class Theme < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.parent_theme_ids
|
||||
get_set_cache "parent_theme_ids" do
|
||||
Theme.where(component: false).pluck(:id)
|
||||
end
|
||||
end
|
||||
|
||||
def self.is_parent_theme?(id)
|
||||
self.parent_theme_ids.include?(id)
|
||||
end
|
||||
|
||||
def self.user_theme_ids
|
||||
get_set_cache "user_theme_ids" do
|
||||
Theme.user_selectable.pluck(:id)
|
||||
|
@ -188,25 +201,22 @@ class Theme < ActiveRecord::Base
|
|||
expire_site_cache!
|
||||
end
|
||||
|
||||
def self.transform_ids(ids, extend: true)
|
||||
return [] if ids.nil?
|
||||
get_set_cache "#{extend ? "extended_" : ""}transformed_ids_#{ids.join("_")}" do
|
||||
next [] if ids.blank?
|
||||
def self.transform_ids(id)
|
||||
return [] if id.blank?
|
||||
|
||||
ids = ids.dup
|
||||
ids.uniq!
|
||||
parent = ids.shift
|
||||
|
||||
components = ids
|
||||
components.push(*components_for(parent)) if extend
|
||||
components.sort!.uniq!
|
||||
|
||||
all_ids = [parent, *components]
|
||||
get_set_cache "transformed_ids_#{id}" do
|
||||
all_ids =
|
||||
if self.is_parent_theme?(id)
|
||||
components = components_for(id).tap { |c| c.sort!.uniq! }
|
||||
[id, *components]
|
||||
else
|
||||
[id]
|
||||
end
|
||||
|
||||
disabled_ids = Theme.where(id: all_ids)
|
||||
.includes(:remote_theme)
|
||||
.select { |t| !t.supported? || !t.enabled? }
|
||||
.pluck(:id)
|
||||
.map(&:id)
|
||||
|
||||
all_ids - disabled_ids
|
||||
end
|
||||
|
@ -272,11 +282,10 @@ class Theme < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.lookup_field(theme_ids, target, field, skip_transformation: false)
|
||||
return if theme_ids.blank?
|
||||
theme_ids = [theme_ids] unless Array === theme_ids
|
||||
def self.lookup_field(theme_id, target, field, skip_transformation: false)
|
||||
return "" if theme_id.blank?
|
||||
|
||||
theme_ids = transform_ids(theme_ids) if !skip_transformation
|
||||
theme_ids = !skip_transformation ? transform_ids(theme_id) : [theme_id]
|
||||
cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{Theme.compiler_version}"
|
||||
lookup = @cache[cache_key]
|
||||
return lookup.html_safe if lookup
|
||||
|
@ -289,8 +298,8 @@ class Theme < ActiveRecord::Base
|
|||
|
||||
def self.lookup_modifier(theme_ids, modifier_name)
|
||||
theme_ids = [theme_ids] unless Array === theme_ids
|
||||
|
||||
theme_ids = transform_ids(theme_ids)
|
||||
|
||||
get_set_cache("#{theme_ids.join(",")}:modifier:#{modifier_name}:#{Theme.compiler_version}") do
|
||||
ThemeModifierSet.resolve_modifier_for_themes(theme_ids, modifier_name)
|
||||
end
|
||||
|
@ -335,14 +344,18 @@ class Theme < ActiveRecord::Base
|
|||
|
||||
def notify_theme_change(with_scheme: false)
|
||||
DB.after_commit do
|
||||
theme_ids = Theme.transform_ids([id])
|
||||
theme_ids = Theme.transform_ids(id)
|
||||
self.class.notify_theme_change(theme_ids, with_scheme: with_scheme)
|
||||
end
|
||||
end
|
||||
|
||||
def self.refresh_message_for_targets(targets, theme_ids)
|
||||
targets.map do |target|
|
||||
Stylesheet::Manager.stylesheet_data(target.to_sym, theme_ids)
|
||||
theme_ids = [theme_ids] unless theme_ids === Array
|
||||
|
||||
targets.each_with_object([]) do |target, data|
|
||||
theme_ids.each do |theme_id|
|
||||
data << Stylesheet::Manager.new(theme_id: theme_id).stylesheet_data(target.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -385,7 +398,8 @@ class Theme < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def list_baked_fields(target, name)
|
||||
theme_ids = Theme.transform_ids([id], extend: name == :color_definitions)
|
||||
theme_ids = Theme.transform_ids(id)
|
||||
theme_ids = [theme_ids.first] if name != :color_definitions
|
||||
self.class.list_baked_fields(theme_ids, target, name)
|
||||
end
|
||||
|
||||
|
@ -435,7 +449,7 @@ class Theme < ActiveRecord::Base
|
|||
|
||||
def all_theme_variables
|
||||
fields = {}
|
||||
ids = Theme.transform_ids([id])
|
||||
ids = Theme.transform_ids(id)
|
||||
ThemeField.find_by_theme_ids(ids).where(type_id: ThemeField.theme_var_type_ids).each do |field|
|
||||
next if fields.key?(field.name)
|
||||
fields[field.name] = field
|
||||
|
@ -530,7 +544,7 @@ class Theme < ActiveRecord::Base
|
|||
def included_settings
|
||||
hash = {}
|
||||
|
||||
Theme.where(id: Theme.transform_ids([id])).each do |theme|
|
||||
Theme.where(id: Theme.transform_ids(id)).each do |theme|
|
||||
hash.merge!(theme.cached_settings)
|
||||
end
|
||||
|
||||
|
@ -641,11 +655,6 @@ class Theme < ActiveRecord::Base
|
|||
contents
|
||||
end
|
||||
|
||||
def has_scss(target)
|
||||
name = target == :embedded_theme ? :embedded_scss : :scss
|
||||
list_baked_fields(target, name).count > 0
|
||||
end
|
||||
|
||||
def convert_settings
|
||||
settings.each do |setting|
|
||||
setting_row = ThemeSetting.where(theme_id: self.id, name: setting.name.to_s).first
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
<%= discourse_stylesheet_link_tag(file) %>
|
||||
<%- end %>
|
||||
|
||||
<%- if theme_ids.present? %>
|
||||
<%- if theme_id.present? %>
|
||||
<%= discourse_stylesheet_link_tag(mobile_view? ? :mobile_theme : :desktop_theme) %>
|
||||
<%- end %>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<%= discourse_stylesheet_link_tag(file) %>
|
||||
<%- end %>
|
||||
|
||||
<%- if theme_ids.present? %>
|
||||
<%- if theme_id.present? %>
|
||||
<%= discourse_stylesheet_link_tag(mobile_view? ? :mobile_theme : :desktop_theme) %>
|
||||
<%- end %>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="utf-8">
|
||||
<title><%= content_for?(:title) ? yield(:title) : SiteSetting.title %></title>
|
||||
<meta name="description" content="<%= @description_meta || SiteSetting.site_description %>">
|
||||
<meta name="discourse_theme_ids" content="<%= theme_ids&.join(",") %>">
|
||||
<meta name="discourse_theme_ids" content="<%= theme_id %>">
|
||||
<meta name="discourse_current_homepage" content="<%= current_homepage %>">
|
||||
<%= render partial: "layouts/head" %>
|
||||
<%= discourse_csrf_tags %>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<%- else %>
|
||||
<%= discourse_stylesheet_link_tag(mobile_view? ? :mobile : :desktop) %>
|
||||
<%- end %>
|
||||
<%- if theme_ids.present? %>
|
||||
<%- if theme_id.present? %>
|
||||
<%= discourse_stylesheet_link_tag(mobile_view? ? :mobile_theme : :desktop_theme) %>
|
||||
<%- end %>
|
||||
<%= theme_lookup("head_tag") %>
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
<%= build_plugin_html 'server:before-head-close' %>
|
||||
</head>
|
||||
<body class="no-ember <%= @custom_body_class %>">
|
||||
<%= theme_lookup("header") %>
|
||||
<%= build_plugin_html 'server:header' %>
|
||||
<%- unless customization_disabled? %>
|
||||
<%= theme_lookup("header") %>
|
||||
<%= build_plugin_html 'server:header' %>
|
||||
<%- end %>
|
||||
|
||||
<section id='main'>
|
||||
<%= render partial: 'header', locals: { hide_auth_buttons: local_assigns[:hide_auth_buttons] } %>
|
||||
<div id="main-outlet" class="<%= @container_class ? @container_class : 'wrap' %>">
|
||||
|
|
|
@ -519,7 +519,7 @@ Discourse::Application.routes.draw do
|
|||
|
||||
get "letter_avatar_proxy/:version/letter/:letter/:color/:size.png" => "user_avatars#show_proxy_letter", constraints: { format: :png }
|
||||
|
||||
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/:hostname/svg-:theme_id-:version.js" => "svg_sprite#show", constraints: { hostname: /[\w\.-]+/, version: /\h{40}/, theme_id: /([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{3}{1,2})/, format: :svg }
|
||||
|
|
|
@ -4,15 +4,15 @@ require 'content_security_policy/extension'
|
|||
|
||||
class ContentSecurityPolicy
|
||||
class << self
|
||||
def policy(theme_ids = [], base_url: Discourse.base_url, path_info: "/")
|
||||
new.build(theme_ids, base_url: base_url, path_info: path_info)
|
||||
def policy(theme_id = nil, base_url: Discourse.base_url, path_info: "/")
|
||||
new.build(theme_id, base_url: base_url, path_info: path_info)
|
||||
end
|
||||
end
|
||||
|
||||
def build(theme_ids, base_url:, path_info: "/")
|
||||
def build(theme_id, base_url:, path_info: "/")
|
||||
builder = Builder.new(base_url: base_url)
|
||||
|
||||
Extension.theme_extensions(theme_ids).each { |extension| builder << extension }
|
||||
Extension.theme_extensions(theme_id).each { |extension| builder << extension }
|
||||
Extension.plugin_extensions.each { |extension| builder << extension }
|
||||
builder << Extension.site_setting_extension
|
||||
builder << Extension.path_specific_extension(path_info)
|
||||
|
|
|
@ -25,9 +25,9 @@ class ContentSecurityPolicy
|
|||
|
||||
THEME_SETTING = 'extend_content_security_policy'
|
||||
|
||||
def theme_extensions(theme_ids)
|
||||
key = "theme_extensions_#{Theme.transform_ids(theme_ids).join(',')}"
|
||||
cache[key] ||= find_theme_extensions(theme_ids)
|
||||
def theme_extensions(theme_id)
|
||||
key = "theme_extensions_#{theme_id}"
|
||||
cache[key] ||= find_theme_extensions(theme_id)
|
||||
end
|
||||
|
||||
def clear_theme_extensions_cache!
|
||||
|
@ -40,12 +40,11 @@ class ContentSecurityPolicy
|
|||
@cache ||= DistributedCache.new('csp_extensions')
|
||||
end
|
||||
|
||||
def find_theme_extensions(theme_ids)
|
||||
def find_theme_extensions(theme_id)
|
||||
extensions = []
|
||||
theme_ids = Theme.transform_ids(theme_id)
|
||||
|
||||
resolved_ids = Theme.transform_ids(theme_ids)
|
||||
|
||||
Theme.where(id: resolved_ids).find_each do |theme|
|
||||
Theme.where(id: theme_ids).find_each do |theme|
|
||||
theme.cached_settings.each do |setting, value|
|
||||
extensions << build_theme_extension(value.split("|")) if setting.to_s == THEME_SETTING
|
||||
end
|
||||
|
@ -54,7 +53,7 @@ class ContentSecurityPolicy
|
|||
extensions << build_theme_extension(ThemeModifierHelper.new(theme_ids: theme_ids).csp_extensions)
|
||||
|
||||
html_fields = ThemeField.where(
|
||||
theme_id: resolved_ids,
|
||||
theme_id: theme_ids,
|
||||
target_id: ThemeField.basic_targets.map { |target| Theme.targets[target.to_sym] },
|
||||
name: ThemeField.html_fields
|
||||
)
|
||||
|
|
|
@ -17,10 +17,10 @@ class ContentSecurityPolicy
|
|||
protocol = (SiteSetting.force_https || request.ssl?) ? "https://" : "http://"
|
||||
base_url = protocol + request.host_with_port + Discourse.base_path
|
||||
|
||||
theme_ids = env[:resolved_theme_ids]
|
||||
theme_id = env[:resolved_theme_id]
|
||||
|
||||
headers['Content-Security-Policy'] = policy(theme_ids, base_url: base_url, path_info: env["PATH_INFO"]) if SiteSetting.content_security_policy
|
||||
headers['Content-Security-Policy-Report-Only'] = policy(theme_ids, base_url: base_url, path_info: env["PATH_INFO"]) if SiteSetting.content_security_policy_report_only
|
||||
headers['Content-Security-Policy'] = policy(theme_id, base_url: base_url, path_info: env["PATH_INFO"]) if SiteSetting.content_security_policy
|
||||
headers['Content-Security-Policy-Report-Only'] = policy(theme_id, base_url: base_url, path_info: env["PATH_INFO"]) if SiteSetting.content_security_policy_report_only
|
||||
|
||||
response
|
||||
end
|
||||
|
|
|
@ -132,9 +132,9 @@ module Middleware
|
|||
|
||||
def theme_ids
|
||||
ids, _ = @request.cookies['theme_ids']&.split('|')
|
||||
ids = ids&.split(",")&.map(&:to_i)
|
||||
if ids && Guardian.new.allow_themes?(ids)
|
||||
Theme.transform_ids(ids)
|
||||
id = ids&.split(",")&.map(&:to_i)&.first
|
||||
if id && Guardian.new.allow_themes?([id])
|
||||
Theme.transform_ids(id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
@ -101,7 +101,7 @@ module Stylesheet
|
|||
end
|
||||
|
||||
theme_id = @theme_id || SiteSetting.default_theme_id
|
||||
resolved_ids = Theme.transform_ids([theme_id])
|
||||
resolved_ids = Theme.transform_ids(theme_id)
|
||||
|
||||
if resolved_ids
|
||||
theme = Theme.find_by_id(theme_id)
|
||||
|
|
|
@ -13,7 +13,7 @@ class Stylesheet::Manager
|
|||
THEME_REGEX ||= /_theme$/
|
||||
COLOR_SCHEME_STYLESHEET ||= "color_definitions"
|
||||
|
||||
@lock = Mutex.new
|
||||
@@lock = Mutex.new
|
||||
|
||||
def self.cache
|
||||
@cache ||= DistributedCache.new("discourse_stylesheet")
|
||||
|
@ -35,117 +35,6 @@ class Stylesheet::Manager
|
|||
cache.hash.keys.select { |k| k =~ /#{plugin}/ }.each { |k| cache.delete(k) }
|
||||
end
|
||||
|
||||
def self.stylesheet_data(target = :desktop, theme_ids = :missing)
|
||||
stylesheet_details(target, "all", theme_ids)
|
||||
end
|
||||
|
||||
def self.stylesheet_link_tag(target = :desktop, media = 'all', theme_ids = :missing)
|
||||
stylesheets = stylesheet_details(target, media, theme_ids)
|
||||
stylesheets.map do |stylesheet|
|
||||
href = stylesheet[:new_href]
|
||||
theme_id = stylesheet[:theme_id]
|
||||
data_theme_id = theme_id ? "data-theme-id=\"#{theme_id}\"" : ""
|
||||
%[<link href="#{href}" media="#{media}" rel="stylesheet" data-target="#{target}" #{data_theme_id}/>]
|
||||
end.join("\n").html_safe
|
||||
end
|
||||
|
||||
def self.stylesheet_details(target = :desktop, media = 'all', theme_ids = :missing)
|
||||
if theme_ids == :missing
|
||||
theme_ids = [SiteSetting.default_theme_id]
|
||||
end
|
||||
|
||||
target = target.to_sym
|
||||
|
||||
theme_ids = [theme_ids] unless Array === theme_ids
|
||||
theme_ids = [theme_ids.first] unless target =~ THEME_REGEX
|
||||
include_components = !!(target =~ THEME_REGEX)
|
||||
|
||||
theme_ids = Theme.transform_ids(theme_ids, extend: include_components)
|
||||
|
||||
current_hostname = Discourse.current_hostname
|
||||
|
||||
array_cache_key = "array_themes_#{theme_ids.join(",")}_#{target}_#{current_hostname}"
|
||||
stylesheets = cache[array_cache_key]
|
||||
return stylesheets if stylesheets.present?
|
||||
|
||||
@lock.synchronize do
|
||||
stylesheets = []
|
||||
theme_ids.each do |theme_id|
|
||||
data = { target: target }
|
||||
cache_key = "path_#{target}_#{theme_id}_#{current_hostname}"
|
||||
href = cache[cache_key]
|
||||
|
||||
unless href
|
||||
builder = self.new(target, theme_id)
|
||||
is_theme = builder.is_theme?
|
||||
has_theme = builder.theme.present?
|
||||
|
||||
if is_theme && !has_theme
|
||||
next
|
||||
else
|
||||
next if builder.theme&.component && !builder.theme&.has_scss(target)
|
||||
data[:theme_id] = builder.theme.id if has_theme && is_theme
|
||||
builder.compile unless File.exists?(builder.stylesheet_fullpath)
|
||||
href = builder.stylesheet_path(current_hostname)
|
||||
end
|
||||
|
||||
cache.defer_set(cache_key, href)
|
||||
end
|
||||
|
||||
data[:theme_id] = theme_id if theme_id.present? && data[:theme_id].blank?
|
||||
data[:new_href] = href
|
||||
stylesheets << data
|
||||
end
|
||||
|
||||
cache.defer_set(array_cache_key, stylesheets.freeze)
|
||||
stylesheets
|
||||
end
|
||||
end
|
||||
|
||||
def self.color_scheme_stylesheet_details(color_scheme_id = nil, media, theme_id)
|
||||
theme_id = theme_id || SiteSetting.default_theme_id
|
||||
|
||||
color_scheme = begin
|
||||
ColorScheme.find(color_scheme_id)
|
||||
rescue
|
||||
# don't load fallback when requesting dark color scheme
|
||||
return false if media != "all"
|
||||
|
||||
Theme.find_by_id(theme_id)&.color_scheme || ColorScheme.base
|
||||
end
|
||||
|
||||
return false if !color_scheme
|
||||
|
||||
target = COLOR_SCHEME_STYLESHEET.to_sym
|
||||
current_hostname = Discourse.current_hostname
|
||||
cache_key = color_scheme_cache_key(color_scheme, theme_id)
|
||||
stylesheets = cache[cache_key]
|
||||
return stylesheets if stylesheets.present?
|
||||
|
||||
stylesheet = { color_scheme_id: color_scheme&.id }
|
||||
|
||||
builder = self.new(target, theme_id, color_scheme)
|
||||
|
||||
builder.compile unless File.exists?(builder.stylesheet_fullpath)
|
||||
|
||||
href = builder.stylesheet_path(current_hostname)
|
||||
stylesheet[:new_href] = href
|
||||
cache.defer_set(cache_key, stylesheet.freeze)
|
||||
stylesheet
|
||||
end
|
||||
|
||||
def self.color_scheme_stylesheet_link_tag(color_scheme_id = nil, media = 'all', theme_ids = nil)
|
||||
theme_id = theme_ids&.first
|
||||
stylesheet = color_scheme_stylesheet_details(color_scheme_id, media, theme_id)
|
||||
return '' if !stylesheet
|
||||
|
||||
href = stylesheet[:new_href]
|
||||
|
||||
css_class = media == 'all' ? "light-scheme" : "dark-scheme"
|
||||
|
||||
%[<link href="#{href}" media="#{media}" rel="stylesheet" class="#{css_class}"/>].html_safe
|
||||
end
|
||||
|
||||
def self.color_scheme_cache_key(color_scheme, theme_id = nil)
|
||||
color_scheme_name = Slug.for(color_scheme.name) + color_scheme&.id.to_s
|
||||
theme_string = theme_id ? "_theme#{theme_id}" : ""
|
||||
|
@ -164,24 +53,30 @@ class Stylesheet::Manager
|
|||
targets += Discourse.find_plugin_css_assets(include_disabled: true, mobile_view: true, desktop_view: true)
|
||||
|
||||
themes.each do |id, name, color_scheme_id|
|
||||
targets.each do |target|
|
||||
theme_id = id || SiteSetting.default_theme_id
|
||||
theme_id = id || SiteSetting.default_theme_id
|
||||
manager = self.new(theme_id: theme_id)
|
||||
|
||||
targets.each do |target|
|
||||
if target =~ THEME_REGEX
|
||||
next if theme_id == -1
|
||||
|
||||
theme_ids = Theme.transform_ids([theme_id], extend: true)
|
||||
scss_checker = ScssChecker.new(target, manager.theme_ids)
|
||||
|
||||
manager.load_themes(manager.theme_ids).each do |theme|
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: target, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
theme_ids.each do |t_id|
|
||||
builder = self.new(target, t_id)
|
||||
STDERR.puts "precompile target: #{target} #{builder.theme.name}"
|
||||
next if builder.theme.component && !builder.theme.has_scss(target)
|
||||
next if theme.component && !scss_checker.has_scss(theme.id)
|
||||
builder.compile(force: true)
|
||||
end
|
||||
else
|
||||
STDERR.puts "precompile target: #{target} #{name}"
|
||||
builder = self.new(target, theme_id)
|
||||
builder.compile(force: true)
|
||||
|
||||
Stylesheet::Manager::Builder.new(
|
||||
target: target, theme: manager.get_theme(theme_id), manager: manager
|
||||
).compile(force: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -190,8 +85,12 @@ class Stylesheet::Manager
|
|||
[theme_color_scheme, *color_schemes].uniq.each do |scheme|
|
||||
STDERR.puts "precompile target: #{COLOR_SCHEME_STYLESHEET} #{name} (#{scheme.name})"
|
||||
|
||||
builder = self.new(COLOR_SCHEME_STYLESHEET, id, scheme)
|
||||
builder.compile(force: true)
|
||||
Stylesheet::Manager::Builder.new(
|
||||
target: COLOR_SCHEME_STYLESHEET,
|
||||
theme: manager.get_theme(theme_id),
|
||||
color_scheme: scheme,
|
||||
manager: manager
|
||||
).compile(force: true)
|
||||
end
|
||||
clear_color_scheme_cache!
|
||||
end
|
||||
|
@ -232,245 +131,165 @@ class Stylesheet::Manager
|
|||
"#{Rails.root}/#{CACHE_PATH}"
|
||||
end
|
||||
|
||||
def initialize(target = :desktop, theme_id = nil, color_scheme = nil)
|
||||
@target = target
|
||||
@theme_id = theme_id
|
||||
@color_scheme = color_scheme
|
||||
attr_reader :theme_ids
|
||||
|
||||
def initialize(theme_id: nil)
|
||||
@theme_id = theme_id || SiteSetting.default_theme_id
|
||||
@theme_ids = Theme.transform_ids(@theme_id)
|
||||
@themes_cache = {}
|
||||
end
|
||||
|
||||
def compile(opts = {})
|
||||
unless opts[:force]
|
||||
if File.exists?(stylesheet_fullpath)
|
||||
unless StylesheetCache.where(target: qualified_target, digest: digest).exists?
|
||||
begin
|
||||
source_map = begin
|
||||
File.read(source_map_fullpath)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
def cache
|
||||
self.class.cache
|
||||
end
|
||||
|
||||
StylesheetCache.add(qualified_target, digest, File.read(stylesheet_fullpath), source_map)
|
||||
rescue => e
|
||||
Rails.logger.warn "Completely unexpected error adding contents of '#{stylesheet_fullpath}' to cache #{e}"
|
||||
end
|
||||
def get_theme(theme_id)
|
||||
if theme = @themes_cache[theme_id]
|
||||
theme
|
||||
else
|
||||
load_themes([theme_id]).first
|
||||
end
|
||||
end
|
||||
|
||||
def load_themes(theme_ids)
|
||||
themes = []
|
||||
to_load_theme_ids = []
|
||||
|
||||
theme_ids.each do |theme_id|
|
||||
if @themes_cache[theme_id]
|
||||
themes << @themes_cache[theme_id]
|
||||
else
|
||||
to_load_theme_ids << theme_id
|
||||
end
|
||||
end
|
||||
|
||||
Theme
|
||||
.where(id: to_load_theme_ids)
|
||||
.includes(:yaml_theme_fields, :theme_settings, :upload_fields, :builder_theme_fields)
|
||||
.each do |theme|
|
||||
|
||||
@themes_cache[theme.id] = theme
|
||||
themes << theme
|
||||
end
|
||||
|
||||
themes
|
||||
end
|
||||
|
||||
def stylesheet_data(target = :desktop)
|
||||
stylesheet_details(target, "all")
|
||||
end
|
||||
|
||||
def stylesheet_link_tag(target = :desktop, media = 'all')
|
||||
stylesheets = stylesheet_details(target, media)
|
||||
|
||||
stylesheets.map do |stylesheet|
|
||||
href = stylesheet[:new_href]
|
||||
theme_id = stylesheet[:theme_id]
|
||||
data_theme_id = theme_id ? "data-theme-id=\"#{theme_id}\"" : ""
|
||||
%[<link href="#{href}" media="#{media}" rel="stylesheet" data-target="#{target}" #{data_theme_id}/>]
|
||||
end.join("\n").html_safe
|
||||
end
|
||||
|
||||
def stylesheet_details(target = :desktop, media = 'all')
|
||||
target = target.to_sym
|
||||
current_hostname = Discourse.current_hostname
|
||||
|
||||
array_cache_key = "array_themes_#{@theme_ids.join(",")}_#{target}_#{current_hostname}"
|
||||
stylesheets = cache[array_cache_key]
|
||||
return stylesheets if stylesheets.present?
|
||||
|
||||
@@lock.synchronize do
|
||||
stylesheets = []
|
||||
stale_theme_ids = []
|
||||
|
||||
@theme_ids.each do |theme_id|
|
||||
cache_key = "path_#{target}_#{theme_id}_#{current_hostname}"
|
||||
|
||||
if href = cache[cache_key]
|
||||
stylesheets << {
|
||||
target: target,
|
||||
theme_id: theme_id,
|
||||
new_href: href
|
||||
}
|
||||
else
|
||||
stale_theme_ids << theme_id
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
rtl = @target.to_s =~ /_rtl$/
|
||||
css, source_map = with_load_paths do |load_paths|
|
||||
Stylesheet::Compiler.compile_asset(
|
||||
@target,
|
||||
rtl: rtl,
|
||||
theme_id: theme&.id,
|
||||
theme_variables: theme&.scss_variables.to_s,
|
||||
source_map_file: source_map_filename,
|
||||
color_scheme_id: @color_scheme&.id,
|
||||
load_paths: load_paths
|
||||
)
|
||||
rescue SassC::SyntaxError => e
|
||||
if Stylesheet::Importer::THEME_TARGETS.include?(@target.to_s)
|
||||
# no special errors for theme, handled in theme editor
|
||||
["", nil]
|
||||
elsif @target.to_s == COLOR_SCHEME_STYLESHEET
|
||||
# log error but do not crash for errors in color definitions SCSS
|
||||
Rails.logger.error "SCSS compilation error: #{e.message}"
|
||||
["", nil]
|
||||
else
|
||||
raise Discourse::ScssError, e.message
|
||||
scss_checker = ScssChecker.new(target, stale_theme_ids)
|
||||
|
||||
load_themes(stale_theme_ids).each do |theme|
|
||||
theme_id = theme.id
|
||||
data = { target: target, theme_id: theme_id }
|
||||
builder = Builder.new(target: target, theme: theme, manager: self)
|
||||
|
||||
next if builder.theme.component && !scss_checker.has_scss(theme_id)
|
||||
builder.compile unless File.exists?(builder.stylesheet_fullpath)
|
||||
href = builder.stylesheet_path(current_hostname)
|
||||
|
||||
cache.defer_set("path_#{target}_#{theme_id}_#{current_hostname}", href)
|
||||
|
||||
data[:new_href] = href
|
||||
stylesheets << data
|
||||
end
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p(cache_fullpath)
|
||||
|
||||
File.open(stylesheet_fullpath, "w") do |f|
|
||||
f.puts css
|
||||
end
|
||||
|
||||
if source_map.present?
|
||||
File.open(source_map_fullpath, "w") do |f|
|
||||
f.puts source_map
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
StylesheetCache.add(qualified_target, digest, css, source_map)
|
||||
rescue => e
|
||||
Rails.logger.warn "Completely unexpected error adding item to cache #{e}"
|
||||
end
|
||||
css
|
||||
end
|
||||
|
||||
def cache_fullpath
|
||||
self.class.cache_fullpath
|
||||
end
|
||||
|
||||
def stylesheet_fullpath
|
||||
"#{cache_fullpath}/#{stylesheet_filename}"
|
||||
end
|
||||
|
||||
def source_map_fullpath
|
||||
"#{cache_fullpath}/#{source_map_filename}"
|
||||
end
|
||||
|
||||
def source_map_filename
|
||||
"#{stylesheet_filename}.map"
|
||||
end
|
||||
|
||||
def stylesheet_fullpath_no_digest
|
||||
"#{cache_fullpath}/#{stylesheet_filename_no_digest}"
|
||||
end
|
||||
|
||||
def stylesheet_cdnpath(hostname)
|
||||
"#{GlobalSetting.cdn_url}#{stylesheet_relpath}?__ws=#{hostname}"
|
||||
end
|
||||
|
||||
def stylesheet_path(hostname)
|
||||
stylesheet_cdnpath(hostname)
|
||||
end
|
||||
|
||||
def root_path
|
||||
"#{GlobalSetting.relative_url_root}/"
|
||||
end
|
||||
|
||||
def stylesheet_relpath
|
||||
"#{root_path}stylesheets/#{stylesheet_filename}"
|
||||
end
|
||||
|
||||
def stylesheet_relpath_no_digest
|
||||
"#{root_path}stylesheets/#{stylesheet_filename_no_digest}"
|
||||
end
|
||||
|
||||
def qualified_target
|
||||
if is_theme?
|
||||
"#{@target}_#{theme.id}"
|
||||
elsif @color_scheme
|
||||
"#{@target}_#{scheme_slug}_#{@color_scheme&.id.to_s}"
|
||||
else
|
||||
scheme_string = theme && theme.color_scheme ? "_#{theme.color_scheme.id}" : ""
|
||||
"#{@target}#{scheme_string}"
|
||||
cache.defer_set(array_cache_key, stylesheets.freeze)
|
||||
stylesheets
|
||||
end
|
||||
end
|
||||
|
||||
def stylesheet_filename(with_digest = true)
|
||||
digest_string = "_#{self.digest}" if with_digest
|
||||
"#{qualified_target}#{digest_string}.css"
|
||||
end
|
||||
def color_scheme_stylesheet_details(color_scheme_id = nil, media)
|
||||
theme_id = @theme_ids.first
|
||||
|
||||
def stylesheet_filename_no_digest
|
||||
stylesheet_filename(_with_digest = false)
|
||||
end
|
||||
color_scheme = begin
|
||||
ColorScheme.find(color_scheme_id)
|
||||
rescue
|
||||
# don't load fallback when requesting dark color scheme
|
||||
return false if media != "all"
|
||||
|
||||
def is_theme?
|
||||
!!(@target.to_s =~ THEME_REGEX)
|
||||
end
|
||||
|
||||
def scheme_slug
|
||||
Slug.for(ActiveSupport::Inflector.transliterate(@color_scheme.name), 'scheme')
|
||||
end
|
||||
|
||||
# digest encodes the things that trigger a recompile
|
||||
def digest
|
||||
@digest ||= begin
|
||||
if is_theme?
|
||||
theme_digest
|
||||
else
|
||||
color_scheme_digest
|
||||
end
|
||||
get_theme(theme_id)&.color_scheme || ColorScheme.base
|
||||
end
|
||||
end
|
||||
|
||||
def theme
|
||||
@theme ||= Theme.find_by(id: @theme_id) || :nil
|
||||
@theme == :nil ? nil : @theme
|
||||
end
|
||||
return false if !color_scheme
|
||||
|
||||
target = COLOR_SCHEME_STYLESHEET.to_sym
|
||||
current_hostname = Discourse.current_hostname
|
||||
cache_key = self.class.color_scheme_cache_key(color_scheme, theme_id)
|
||||
stylesheets = cache[cache_key]
|
||||
return stylesheets if stylesheets.present?
|
||||
|
||||
stylesheet = { color_scheme_id: color_scheme.id }
|
||||
|
||||
theme = get_theme(theme_id)
|
||||
|
||||
def with_load_paths
|
||||
if theme
|
||||
theme.with_scss_load_paths { |p| yield p }
|
||||
builder = Builder.new(
|
||||
target: target,
|
||||
theme: get_theme(theme_id),
|
||||
color_scheme: color_scheme,
|
||||
manager: self
|
||||
)
|
||||
|
||||
builder.compile unless File.exists?(builder.stylesheet_fullpath)
|
||||
|
||||
href = builder.stylesheet_path(current_hostname)
|
||||
stylesheet[:new_href] = href
|
||||
cache.defer_set(cache_key, stylesheet.freeze)
|
||||
stylesheet
|
||||
else
|
||||
yield nil
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def theme_digest
|
||||
if [:mobile_theme, :desktop_theme].include?(@target)
|
||||
scss_digest = theme.resolve_baked_field(@target.to_s.sub("_theme", ""), :scss)
|
||||
elsif @target == :embedded_theme
|
||||
scss_digest = theme.resolve_baked_field(:common, :embedded_scss)
|
||||
else
|
||||
raise "attempting to look up theme digest for invalid field"
|
||||
end
|
||||
def color_scheme_stylesheet_link_tag(color_scheme_id = nil, media = 'all')
|
||||
stylesheet = color_scheme_stylesheet_details(color_scheme_id, media)
|
||||
|
||||
Digest::SHA1.hexdigest(scss_digest.to_s + color_scheme_digest.to_s + settings_digest + plugins_digest + uploads_digest)
|
||||
end
|
||||
return '' if !stylesheet
|
||||
|
||||
# this protects us from situations where new versions of a plugin removed a file
|
||||
# old instances may still be serving CSS and not aware of the change
|
||||
# so we could end up poisoning the cache with a bad file that can not be removed
|
||||
def plugins_digest
|
||||
assets = []
|
||||
DiscoursePluginRegistry.stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
DiscoursePluginRegistry.mobile_stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
DiscoursePluginRegistry.desktop_stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
Digest::SHA1.hexdigest(assets.sort.join)
|
||||
end
|
||||
href = stylesheet[:new_href]
|
||||
|
||||
def settings_digest
|
||||
theme_ids = Theme.components_for(@theme_id).dup
|
||||
theme_ids << @theme_id
|
||||
css_class = media == 'all' ? "light-scheme" : "dark-scheme"
|
||||
|
||||
fields = ThemeField.where(
|
||||
name: "yaml",
|
||||
type_id: ThemeField.types[:yaml],
|
||||
theme_id: theme_ids
|
||||
).pluck(:updated_at)
|
||||
|
||||
settings = ThemeSetting.where(theme_id: theme_ids).pluck(:updated_at)
|
||||
timestamps = fields.concat(settings).map!(&:to_f).sort!.join(",")
|
||||
|
||||
Digest::SHA1.hexdigest(timestamps)
|
||||
end
|
||||
|
||||
def uploads_digest
|
||||
sha1s =
|
||||
if (theme_ids = theme&.all_theme_variables).present?
|
||||
ThemeField
|
||||
.joins(:upload)
|
||||
.where(id: theme_ids)
|
||||
.pluck(:sha1)
|
||||
.join(",")
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
Digest::SHA1.hexdigest(sha1s)
|
||||
end
|
||||
|
||||
def color_scheme_digest
|
||||
cs = @color_scheme || theme&.color_scheme
|
||||
|
||||
categories_updated = self.class.cache.defer_get_set("categories_updated") do
|
||||
Category
|
||||
.where("uploaded_background_id IS NOT NULL")
|
||||
.pluck(:updated_at)
|
||||
.map(&:to_i)
|
||||
.sum
|
||||
end
|
||||
|
||||
fonts = "#{SiteSetting.base_font}-#{SiteSetting.heading_font}"
|
||||
|
||||
if cs || categories_updated > 0
|
||||
theme_color_defs = theme&.resolve_baked_field(:common, :color_definitions)
|
||||
Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.last_file_updated}-#{categories_updated}-#{fonts}"
|
||||
else
|
||||
digest_string = "defaults-#{Stylesheet::Manager.last_file_updated}-#{fonts}"
|
||||
|
||||
if cdn_url = GlobalSetting.cdn_url
|
||||
digest_string = "#{digest_string}-#{cdn_url}"
|
||||
end
|
||||
|
||||
Digest::SHA1.hexdigest digest_string
|
||||
end
|
||||
%[<link href="#{href}" media="#{media}" rel="stylesheet" class="#{css_class}"/>].html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Stylesheet::Manager::Builder
|
||||
attr_reader :theme
|
||||
|
||||
def initialize(target: :desktop, theme:, color_scheme: nil, manager:)
|
||||
@target = target
|
||||
@theme = theme
|
||||
@color_scheme = color_scheme
|
||||
@manager = manager
|
||||
end
|
||||
|
||||
def compile(opts = {})
|
||||
if !opts[:force]
|
||||
if File.exists?(stylesheet_fullpath)
|
||||
unless StylesheetCache.where(target: qualified_target, digest: digest).exists?
|
||||
begin
|
||||
source_map = begin
|
||||
File.read(source_map_fullpath)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
|
||||
StylesheetCache.add(qualified_target, digest, File.read(stylesheet_fullpath), source_map)
|
||||
rescue => e
|
||||
Rails.logger.warn "Completely unexpected error adding contents of '#{stylesheet_fullpath}' to cache #{e}"
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
rtl = @target.to_s =~ /_rtl$/
|
||||
css, source_map = with_load_paths do |load_paths|
|
||||
Stylesheet::Compiler.compile_asset(
|
||||
@target,
|
||||
rtl: rtl,
|
||||
theme_id: theme&.id,
|
||||
theme_variables: theme&.scss_variables.to_s,
|
||||
source_map_file: source_map_filename,
|
||||
color_scheme_id: @color_scheme&.id,
|
||||
load_paths: load_paths
|
||||
)
|
||||
rescue SassC::SyntaxError => e
|
||||
if Stylesheet::Importer::THEME_TARGETS.include?(@target.to_s)
|
||||
# no special errors for theme, handled in theme editor
|
||||
["", nil]
|
||||
elsif @target.to_s == Stylesheet::Manager::COLOR_SCHEME_STYLESHEET
|
||||
# log error but do not crash for errors in color definitions SCSS
|
||||
Rails.logger.error "SCSS compilation error: #{e.message}"
|
||||
["", nil]
|
||||
else
|
||||
raise Discourse::ScssError, e.message
|
||||
end
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p(cache_fullpath)
|
||||
|
||||
File.open(stylesheet_fullpath, "w") do |f|
|
||||
f.puts css
|
||||
end
|
||||
|
||||
if source_map.present?
|
||||
File.open(source_map_fullpath, "w") do |f|
|
||||
f.puts source_map
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
StylesheetCache.add(qualified_target, digest, css, source_map)
|
||||
rescue => e
|
||||
Rails.logger.warn "Completely unexpected error adding item to cache #{e}"
|
||||
end
|
||||
css
|
||||
end
|
||||
|
||||
def cache_fullpath
|
||||
Stylesheet::Manager.cache_fullpath
|
||||
end
|
||||
|
||||
def stylesheet_fullpath
|
||||
"#{cache_fullpath}/#{stylesheet_filename}"
|
||||
end
|
||||
|
||||
def source_map_fullpath
|
||||
"#{cache_fullpath}/#{source_map_filename}"
|
||||
end
|
||||
|
||||
def source_map_filename
|
||||
"#{stylesheet_filename}.map"
|
||||
end
|
||||
|
||||
def stylesheet_fullpath_no_digest
|
||||
"#{cache_fullpath}/#{stylesheet_filename_no_digest}"
|
||||
end
|
||||
|
||||
def stylesheet_cdnpath(hostname)
|
||||
"#{GlobalSetting.cdn_url}#{stylesheet_relpath}?__ws=#{hostname}"
|
||||
end
|
||||
|
||||
def stylesheet_path(hostname)
|
||||
stylesheet_cdnpath(hostname)
|
||||
end
|
||||
|
||||
def root_path
|
||||
"#{GlobalSetting.relative_url_root}/"
|
||||
end
|
||||
|
||||
def stylesheet_relpath
|
||||
"#{root_path}stylesheets/#{stylesheet_filename}"
|
||||
end
|
||||
|
||||
def stylesheet_relpath_no_digest
|
||||
"#{root_path}stylesheets/#{stylesheet_filename_no_digest}"
|
||||
end
|
||||
|
||||
def qualified_target
|
||||
if is_theme?
|
||||
"#{@target}_#{theme.id}"
|
||||
elsif @color_scheme
|
||||
"#{@target}_#{scheme_slug}_#{@color_scheme&.id.to_s}"
|
||||
else
|
||||
scheme_string = theme && theme.color_scheme ? "_#{theme.color_scheme.id}" : ""
|
||||
"#{@target}#{scheme_string}"
|
||||
end
|
||||
end
|
||||
|
||||
def stylesheet_filename(with_digest = true)
|
||||
digest_string = "_#{self.digest}" if with_digest
|
||||
"#{qualified_target}#{digest_string}.css"
|
||||
end
|
||||
|
||||
def stylesheet_filename_no_digest
|
||||
stylesheet_filename(_with_digest = false)
|
||||
end
|
||||
|
||||
def is_theme?
|
||||
!!(@target.to_s =~ Stylesheet::Manager::THEME_REGEX)
|
||||
end
|
||||
|
||||
def scheme_slug
|
||||
Slug.for(ActiveSupport::Inflector.transliterate(@color_scheme.name), 'scheme')
|
||||
end
|
||||
|
||||
# digest encodes the things that trigger a recompile
|
||||
def digest
|
||||
@digest ||= begin
|
||||
if is_theme?
|
||||
theme_digest
|
||||
else
|
||||
color_scheme_digest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_load_paths
|
||||
if theme
|
||||
theme.with_scss_load_paths { |p| yield p }
|
||||
else
|
||||
yield nil
|
||||
end
|
||||
end
|
||||
|
||||
def scss_digest
|
||||
if [:mobile_theme, :desktop_theme].include?(@target)
|
||||
resolve_baked_field(@target.to_s.sub("_theme", ""), :scss)
|
||||
elsif @target == :embedded_theme
|
||||
resolve_baked_field(:common, :embedded_scss)
|
||||
else
|
||||
raise "attempting to look up theme digest for invalid field"
|
||||
end
|
||||
end
|
||||
|
||||
def theme_digest
|
||||
Digest::SHA1.hexdigest(scss_digest.to_s + color_scheme_digest.to_s + settings_digest + plugins_digest + uploads_digest)
|
||||
end
|
||||
|
||||
# this protects us from situations where new versions of a plugin removed a file
|
||||
# old instances may still be serving CSS and not aware of the change
|
||||
# so we could end up poisoning the cache with a bad file that can not be removed
|
||||
def plugins_digest
|
||||
assets = []
|
||||
DiscoursePluginRegistry.stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
DiscoursePluginRegistry.mobile_stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
DiscoursePluginRegistry.desktop_stylesheets.each { |_, paths| assets += paths.to_a }
|
||||
Digest::SHA1.hexdigest(assets.sort.join)
|
||||
end
|
||||
|
||||
def settings_digest
|
||||
theme_ids = Theme.is_parent_theme?(theme.id) ? @manager.theme_ids : [theme.id]
|
||||
|
||||
themes =
|
||||
if Theme.is_parent_theme?(theme.id)
|
||||
@manager.load_themes(@manager.theme_ids)
|
||||
else
|
||||
[@manager.get_theme(theme.id)]
|
||||
end
|
||||
|
||||
fields = themes.each_with_object([]) do |theme, array|
|
||||
array.concat(theme.yaml_theme_fields.map(&:updated_at))
|
||||
end
|
||||
|
||||
settings = themes.each_with_object([]) do |theme, array|
|
||||
array.concat(theme.theme_settings.map(&:updated_at))
|
||||
end
|
||||
|
||||
timestamps = fields.concat(settings).map!(&:to_f).sort!.join(",")
|
||||
|
||||
Digest::SHA1.hexdigest(timestamps)
|
||||
end
|
||||
|
||||
def uploads_digest
|
||||
sha1s = []
|
||||
|
||||
theme.upload_fields.map do |upload_field|
|
||||
sha1s << upload_field.upload.sha1
|
||||
end
|
||||
|
||||
Digest::SHA1.hexdigest(sha1s.sort!.join("\n"))
|
||||
end
|
||||
|
||||
def color_scheme_digest
|
||||
cs = @color_scheme || theme&.color_scheme
|
||||
|
||||
categories_updated = Stylesheet::Manager.cache.defer_get_set("categories_updated") do
|
||||
Category
|
||||
.where("uploaded_background_id IS NOT NULL")
|
||||
.pluck(:updated_at)
|
||||
.map(&:to_i)
|
||||
.sum
|
||||
end
|
||||
|
||||
fonts = "#{SiteSetting.base_font}-#{SiteSetting.heading_font}"
|
||||
|
||||
if cs || categories_updated > 0
|
||||
theme_color_defs = resolve_baked_field(:common, :color_definitions)
|
||||
Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.last_file_updated}-#{categories_updated}-#{fonts}"
|
||||
else
|
||||
digest_string = "defaults-#{Stylesheet::Manager.last_file_updated}-#{fonts}"
|
||||
|
||||
if cdn_url = GlobalSetting.cdn_url
|
||||
digest_string = "#{digest_string}-#{cdn_url}"
|
||||
end
|
||||
|
||||
Digest::SHA1.hexdigest digest_string
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_baked_field(target, name)
|
||||
theme_ids =
|
||||
if Theme.is_parent_theme?(theme.id)
|
||||
@manager.theme_ids
|
||||
else
|
||||
[theme.id]
|
||||
end
|
||||
|
||||
theme_ids = [theme_ids.first] if name != :color_definitions
|
||||
|
||||
baked_fields = []
|
||||
targets = [Theme.targets[target.to_sym], Theme.targets[:common]]
|
||||
|
||||
@manager.load_themes(theme_ids).each do |theme|
|
||||
theme.builder_theme_fields.each do |theme_field|
|
||||
if theme_field.name == name.to_s && targets.include?(theme_field.target_id)
|
||||
baked_fields << theme_field
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
baked_fields.map do |f|
|
||||
f.ensure_baked!
|
||||
f.value_baked || f.value
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Stylesheet::Manager::ScssChecker
|
||||
def initialize(target, theme_ids)
|
||||
@target = target.to_sym
|
||||
@theme_ids = theme_ids
|
||||
end
|
||||
|
||||
def has_scss(theme_id)
|
||||
!!get_themes_with_scss[theme_id]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_themes_with_scss
|
||||
@themes_with_scss ||= begin
|
||||
theme_target = @target.to_sym
|
||||
theme_target = :mobile if theme_target == :mobile_theme
|
||||
theme_target = :desktop if theme_target == :desktop_theme
|
||||
name = @target == :embedded_theme ? :embedded_scss : :scss
|
||||
|
||||
results = Theme
|
||||
.where(id: @theme_ids)
|
||||
.left_joins(:theme_fields)
|
||||
.where(theme_fields: {
|
||||
target_id: [Theme.targets[theme_target], Theme.targets[:common]],
|
||||
name: name
|
||||
})
|
||||
.group(:id)
|
||||
.size
|
||||
|
||||
results
|
||||
end
|
||||
end
|
||||
end
|
|
@ -228,12 +228,12 @@ module SvgSprite
|
|||
badge_icons
|
||||
end
|
||||
|
||||
def self.custom_svg_sprites(theme_ids = [])
|
||||
get_set_cache("custom_svg_sprites_#{Theme.transform_ids(theme_ids).join(',')}") do
|
||||
def self.custom_svg_sprites(theme_id)
|
||||
get_set_cache("custom_svg_sprites_#{Theme.transform_ids(theme_id).join(',')}") do
|
||||
custom_sprite_paths = Dir.glob("#{Rails.root}/plugins/*/svg-icons/*.svg")
|
||||
|
||||
if theme_ids.present?
|
||||
ThemeField.where(type_id: ThemeField.types[:theme_upload_var], name: THEME_SPRITE_VAR_NAME, theme_id: Theme.transform_ids(theme_ids))
|
||||
if theme_id.present?
|
||||
ThemeField.where(type_id: ThemeField.types[:theme_upload_var], name: THEME_SPRITE_VAR_NAME, theme_id: Theme.transform_ids(theme_id))
|
||||
.pluck(:upload_id).each do |upload_id|
|
||||
|
||||
upload = Upload.find(upload_id) rescue nil
|
||||
|
@ -253,15 +253,15 @@ module SvgSprite
|
|||
end
|
||||
end
|
||||
|
||||
def self.all_icons(theme_ids = [])
|
||||
get_set_cache("icons_#{Theme.transform_ids(theme_ids).join(',')}") do
|
||||
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_ids))
|
||||
.merge(custom_icons(theme_ids))
|
||||
.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)
|
||||
|
@ -269,25 +269,25 @@ module SvgSprite
|
|||
end
|
||||
end
|
||||
|
||||
def self.version(theme_ids = [])
|
||||
get_set_cache("version_#{Theme.transform_ids(theme_ids).join(',')}") do
|
||||
Digest::SHA1.hexdigest(bundle(theme_ids))
|
||||
def self.version(theme_id = nil)
|
||||
get_set_cache("version_#{Theme.transform_ids(theme_id).join(',')}") do
|
||||
Digest::SHA1.hexdigest(bundle(theme_id))
|
||||
end
|
||||
end
|
||||
|
||||
def self.path(theme_ids = [])
|
||||
"/svg-sprite/#{Discourse.current_hostname}/svg-#{theme_ids&.join(",")}-#{version(theme_ids)}.js"
|
||||
def self.path(theme_id = nil)
|
||||
"/svg-sprite/#{Discourse.current_hostname}/svg-#{theme_id}-#{version(theme_id)}.js"
|
||||
end
|
||||
|
||||
def self.expire_cache
|
||||
cache&.clear
|
||||
end
|
||||
|
||||
def self.sprite_sources(theme_ids)
|
||||
def self.sprite_sources(theme_id)
|
||||
sources = CORE_SVG_SPRITES
|
||||
|
||||
if theme_ids.present?
|
||||
sources = sources + custom_svg_sprites(theme_ids)
|
||||
if theme_id.present?
|
||||
sources = sources + custom_svg_sprites(theme_id)
|
||||
end
|
||||
|
||||
sources
|
||||
|
@ -313,8 +313,8 @@ module SvgSprite
|
|||
end
|
||||
end
|
||||
|
||||
def self.bundle(theme_ids = [])
|
||||
icons = all_icons(theme_ids)
|
||||
def self.bundle(theme_id = nil)
|
||||
icons = all_icons(theme_id)
|
||||
|
||||
svg_subset = """<!--
|
||||
Discourse SVG subset of Font Awesome Free by @fontawesome - https://fontawesome.com
|
||||
|
@ -329,9 +329,9 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
|||
end
|
||||
end
|
||||
|
||||
custom_svg_sprites(theme_ids).each do |fname|
|
||||
custom_svg_sprites(theme_id).each do |fname|
|
||||
if !File.exist?(fname)
|
||||
cache.delete("custom_svg_sprites_#{Theme.transform_ids(theme_ids).join(',')}")
|
||||
cache.delete("custom_svg_sprites_#{Theme.transform_ids(theme_id).join(',')}")
|
||||
next
|
||||
end
|
||||
|
||||
|
@ -461,13 +461,13 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
|||
end
|
||||
end
|
||||
|
||||
def self.theme_icons(theme_ids)
|
||||
return [] if theme_ids.blank?
|
||||
def self.theme_icons(theme_id)
|
||||
return [] if theme_id.blank?
|
||||
|
||||
theme_icon_settings = []
|
||||
|
||||
# Need to load full records for default values
|
||||
Theme.where(id: Theme.transform_ids(theme_ids)).each do |theme|
|
||||
Theme.where(id: Theme.transform_ids(theme_id)).each do |theme|
|
||||
settings = theme.cached_settings.each do |key, value|
|
||||
if key.to_s.include?("_icon") && String === value
|
||||
theme_icon_settings |= value.split('|')
|
||||
|
@ -475,15 +475,15 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
|
|||
end
|
||||
end
|
||||
|
||||
theme_icon_settings |= ThemeModifierHelper.new(theme_ids: theme_ids).svg_icons
|
||||
theme_icon_settings |= ThemeModifierHelper.new(theme_ids: [theme_id]).svg_icons
|
||||
|
||||
theme_icon_settings
|
||||
end
|
||||
|
||||
def self.custom_icons(theme_ids)
|
||||
def self.custom_icons(theme_id)
|
||||
# Automatically register icons in sprites added via themes or plugins
|
||||
icons = []
|
||||
custom_svg_sprites(theme_ids).each do |fname|
|
||||
custom_svg_sprites(theme_id).each do |fname|
|
||||
next if !File.exist?(fname)
|
||||
|
||||
svg_file = Nokogiri::XML(File.open(fname))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
class ThemeModifierHelper
|
||||
def initialize(request: nil, theme_ids: nil)
|
||||
@theme_ids = theme_ids || request&.env&.[](:resolved_theme_ids)
|
||||
@theme_ids = theme_ids || [request&.env&.[](:resolved_theme_id)]
|
||||
end
|
||||
|
||||
ThemeModifierSet.modifiers.keys.each do |modifier|
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Stylesheet::Manager::ScssChecker do
|
||||
fab!(:theme) { Fabricate(:theme) }
|
||||
|
||||
describe '#has_scss' do
|
||||
it 'should return true when theme has scss' do
|
||||
scss_theme = Fabricate(:theme, component: true)
|
||||
scss_theme.set_field(target: :common, name: "scss", value: ".scss{color: red;}")
|
||||
scss_theme.save!
|
||||
|
||||
embedded_scss_theme = Fabricate(:theme, component: true)
|
||||
embedded_scss_theme.set_field(target: :common, name: "embedded_scss", value: ".scss{color: red;}")
|
||||
embedded_scss_theme.save!
|
||||
|
||||
theme_ids = [scss_theme.id, embedded_scss_theme.id]
|
||||
|
||||
desktop_theme_checker = described_class.new(:desktop_theme, theme_ids)
|
||||
|
||||
expect(desktop_theme_checker.has_scss(scss_theme.id)).to eq(true)
|
||||
expect(desktop_theme_checker.has_scss(embedded_scss_theme.id)).to eq(false)
|
||||
|
||||
embedded_theme_checker = described_class.new(:embedded_theme, theme_ids)
|
||||
|
||||
expect(embedded_theme_checker.has_scss(scss_theme.id)).to eq(false)
|
||||
expect(embedded_theme_checker.has_scss(embedded_scss_theme.id)).to eq(true)
|
||||
end
|
||||
|
||||
it 'should return false when theme does not have scss' do
|
||||
expect(described_class.new(:desktop_theme, [theme.id]).has_scss(theme.id))
|
||||
.to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,21 +4,24 @@ require 'rails_helper'
|
|||
require 'stylesheet/compiler'
|
||||
|
||||
describe Stylesheet::Manager do
|
||||
def manager(theme_id = nil)
|
||||
Stylesheet::Manager.new(theme_id: theme_id)
|
||||
end
|
||||
|
||||
it 'does not crash for missing theme' do
|
||||
Theme.clear_default!
|
||||
link = Stylesheet::Manager.stylesheet_link_tag(:embedded_theme)
|
||||
link = manager.stylesheet_link_tag(:embedded_theme)
|
||||
expect(link).to eq("")
|
||||
|
||||
theme = Fabricate(:theme)
|
||||
SiteSetting.default_theme_id = theme.id
|
||||
|
||||
link = Stylesheet::Manager.stylesheet_link_tag(:embedded_theme)
|
||||
link = manager.stylesheet_link_tag(:embedded_theme)
|
||||
expect(link).not_to eq("")
|
||||
end
|
||||
|
||||
it "still returns something for no themes" do
|
||||
link = Stylesheet::Manager.stylesheet_link_tag(:desktop, 'all', [])
|
||||
link = manager.stylesheet_link_tag(:desktop, 'all')
|
||||
expect(link).not_to eq("")
|
||||
end
|
||||
|
||||
|
@ -42,13 +45,17 @@ describe Stylesheet::Manager do
|
|||
}}
|
||||
|
||||
it 'can correctly compile theme css' do
|
||||
old_links = Stylesheet::Manager.stylesheet_link_tag(:desktop_theme, 'all', theme.id)
|
||||
manager = manager(theme.id)
|
||||
old_links = manager.stylesheet_link_tag(:desktop_theme, 'all')
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
manager.compile(force: true)
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
css = File.read(manager.stylesheet_fullpath)
|
||||
_source_map = File.read(manager.source_map_fullpath)
|
||||
builder.compile(force: true)
|
||||
|
||||
css = File.read(builder.stylesheet_fullpath)
|
||||
_source_map = File.read(builder.source_map_fullpath)
|
||||
|
||||
expect(css).to match(/\.common/)
|
||||
expect(css).to match(/\.desktop/)
|
||||
|
@ -57,11 +64,14 @@ describe Stylesheet::Manager do
|
|||
expect(css).not_to match(/child_common/)
|
||||
expect(css).not_to match(/child_desktop/)
|
||||
|
||||
child_theme_manager = Stylesheet::Manager.new(:desktop_theme, child_theme.id)
|
||||
child_theme_manager.compile(force: true)
|
||||
child_theme_builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: child_theme, manager: manager
|
||||
)
|
||||
|
||||
child_css = File.read(child_theme_manager.stylesheet_fullpath)
|
||||
_child_source_map = File.read(child_theme_manager.source_map_fullpath)
|
||||
child_theme_builder.compile(force: true)
|
||||
|
||||
child_css = File.read(child_theme_builder.stylesheet_fullpath)
|
||||
_child_source_map = File.read(child_theme_builder.source_map_fullpath)
|
||||
|
||||
expect(child_css).to match(/child_common/)
|
||||
expect(child_css).to match(/child_desktop/)
|
||||
|
@ -69,7 +79,7 @@ describe Stylesheet::Manager do
|
|||
child_theme.set_field(target: :desktop, name: :scss, value: ".nothing{color: green;}")
|
||||
child_theme.save!
|
||||
|
||||
new_links = Stylesheet::Manager.stylesheet_link_tag(:desktop_theme, 'all', theme.id)
|
||||
new_links = manager(theme.id).stylesheet_link_tag(:desktop_theme, 'all')
|
||||
|
||||
expect(new_links).not_to eq(old_links)
|
||||
|
||||
|
@ -79,30 +89,48 @@ describe Stylesheet::Manager do
|
|||
end
|
||||
|
||||
it 'can correctly compile embedded theme css' do
|
||||
manager = Stylesheet::Manager.new(:embedded_theme, theme.id)
|
||||
manager.compile(force: true)
|
||||
manager = manager(theme.id)
|
||||
|
||||
css = File.read(manager.stylesheet_fullpath)
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :embedded_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
builder.compile(force: true)
|
||||
|
||||
css = File.read(builder.stylesheet_fullpath)
|
||||
expect(css).to match(/\.embedded/)
|
||||
expect(css).not_to match(/\.child_embedded/)
|
||||
|
||||
child_theme_manager = Stylesheet::Manager.new(:embedded_theme, child_theme.id)
|
||||
child_theme_manager.compile(force: true)
|
||||
child_theme_builder = Stylesheet::Manager::Builder.new(
|
||||
target: :embedded_theme,
|
||||
theme: child_theme,
|
||||
manager: manager
|
||||
)
|
||||
|
||||
css = File.read(child_theme_manager.stylesheet_fullpath)
|
||||
child_theme_builder.compile(force: true)
|
||||
|
||||
css = File.read(child_theme_builder.stylesheet_fullpath)
|
||||
expect(css).to match(/\.child_embedded/)
|
||||
end
|
||||
|
||||
it 'includes both parent and child theme assets' do
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:desktop_theme, 'all', [theme.id])
|
||||
expect(hrefs.count).to eq(2)
|
||||
expect(hrefs[0][:theme_id]).to eq(theme.id)
|
||||
expect(hrefs[1][:theme_id]).to eq(child_theme.id)
|
||||
manager = manager(theme.id)
|
||||
|
||||
hrefs = manager.stylesheet_details(:desktop_theme, 'all')
|
||||
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:embedded_theme, 'all', [theme.id])
|
||||
expect(hrefs.count).to eq(2)
|
||||
expect(hrefs[0][:theme_id]).to eq(theme.id)
|
||||
expect(hrefs[1][:theme_id]).to eq(child_theme.id)
|
||||
|
||||
expect(hrefs.map { |href| href[:theme_id] }).to contain_exactly(
|
||||
theme.id, child_theme.id
|
||||
)
|
||||
|
||||
hrefs = manager.stylesheet_details(:embedded_theme, 'all')
|
||||
|
||||
expect(hrefs.count).to eq(2)
|
||||
|
||||
expect(hrefs.map { |href| href[:theme_id] }).to contain_exactly(
|
||||
theme.id, child_theme.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not output tags for component targets with no styles' do
|
||||
|
@ -112,10 +140,12 @@ describe Stylesheet::Manager do
|
|||
|
||||
theme.add_relative_theme!(:child, embedded_scss_child)
|
||||
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:desktop_theme, 'all', [theme.id])
|
||||
manager = manager(theme.id)
|
||||
|
||||
hrefs = manager.stylesheet_details(:desktop_theme, 'all')
|
||||
expect(hrefs.count).to eq(2) # theme + child_theme
|
||||
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:embedded_theme, 'all', [theme.id])
|
||||
hrefs = manager.stylesheet_details(:embedded_theme, 'all')
|
||||
expect(hrefs.count).to eq(3) # theme + child_theme + embedded_scss_child
|
||||
end
|
||||
|
||||
|
@ -125,16 +155,20 @@ describe Stylesheet::Manager do
|
|||
child_with_mobile_scss.save!
|
||||
theme.add_relative_theme!(:child, child_with_mobile_scss)
|
||||
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:mobile_theme, 'all', [theme.id])
|
||||
expect(hrefs.find { |h| h[:theme_id] == child_with_mobile_scss.id }).to be_present
|
||||
manager = manager(theme.id)
|
||||
hrefs = manager.stylesheet_details(:mobile_theme, 'all')
|
||||
|
||||
expect(hrefs.count).to eq(3)
|
||||
expect(hrefs.find { |h| h[:theme_id] == child_with_mobile_scss.id }).to be_present
|
||||
end
|
||||
|
||||
it 'does not output multiple assets for non-theme targets' do
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:admin, 'all', [theme.id])
|
||||
manager = manager()
|
||||
|
||||
hrefs = manager.stylesheet_details(:admin, 'all')
|
||||
expect(hrefs.count).to eq(1)
|
||||
|
||||
hrefs = Stylesheet::Manager.stylesheet_details(:mobile, 'all', [theme.id])
|
||||
hrefs = manager.stylesheet_details(:mobile, 'all')
|
||||
expect(hrefs.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
@ -146,14 +180,21 @@ describe Stylesheet::Manager do
|
|||
|
||||
it 'can correctly account for plugins in digest' do
|
||||
theme = Fabricate(:theme)
|
||||
manager = manager(theme.id)
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest1 = manager.digest
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.digest
|
||||
|
||||
DiscoursePluginRegistry.stylesheets["fake"] = Set.new(["fake_file"])
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest2 = manager.digest
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
digest2 = builder.digest
|
||||
|
||||
expect(digest1).not_to eq(digest2)
|
||||
end
|
||||
|
@ -167,13 +208,23 @@ describe Stylesheet::Manager do
|
|||
child.set_field(target: :common, name: :scss, value: "body {background-color: $childcolor}")
|
||||
child.save!
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest1 = manager.digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.digest
|
||||
|
||||
child.update_setting(:childcolor, "green")
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest2 = manager.digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
digest2 = builder.digest
|
||||
|
||||
expect(digest1).not_to eq(digest2)
|
||||
end
|
||||
|
@ -194,8 +245,13 @@ describe Stylesheet::Manager do
|
|||
type_id: ThemeField.types[:theme_upload_var]
|
||||
)
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest1 = manager.digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.digest
|
||||
field.destroy!
|
||||
|
||||
upload = UploadCreator.new(image2, "logo.png").create_for(-1)
|
||||
|
@ -208,63 +264,93 @@ describe Stylesheet::Manager do
|
|||
type_id: ThemeField.types[:theme_upload_var]
|
||||
)
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest2 = manager.digest
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme.reload, manager: manager
|
||||
)
|
||||
|
||||
digest2 = builder.digest
|
||||
|
||||
expect(digest1).not_to eq(digest2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'color_scheme_digest' do
|
||||
let(:theme) { Fabricate(:theme) }
|
||||
fab!(:theme) { Fabricate(:theme) }
|
||||
|
||||
it "changes with category background image" do
|
||||
category1 = Fabricate(:category, uploaded_background_id: 123, updated_at: 1.week.ago)
|
||||
category2 = Fabricate(:category, uploaded_background_id: 456, updated_at: 2.days.ago)
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
manager = manager(theme.id)
|
||||
|
||||
digest1 = manager.color_scheme_digest
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
category2.update(uploaded_background_id: 789, updated_at: 1.day.ago)
|
||||
digest1 = builder.color_scheme_digest
|
||||
|
||||
digest2 = manager.color_scheme_digest
|
||||
category2.update!(uploaded_background_id: 789, updated_at: 1.day.ago)
|
||||
|
||||
digest2 = builder.color_scheme_digest
|
||||
expect(digest2).to_not eq(digest1)
|
||||
|
||||
category1.update(uploaded_background_id: nil, updated_at: 5.minutes.ago)
|
||||
category1.update!(uploaded_background_id: nil, updated_at: 5.minutes.ago)
|
||||
|
||||
digest3 = manager.color_scheme_digest
|
||||
digest3 = builder.color_scheme_digest
|
||||
expect(digest3).to_not eq(digest2)
|
||||
expect(digest3).to_not eq(digest1)
|
||||
end
|
||||
|
||||
it "updates digest when updating a color scheme" do
|
||||
scheme = ColorScheme.create_from_base(name: "Neutral", base_scheme_id: "Neutral")
|
||||
manager = Stylesheet::Manager.new(:color_definitions, nil, scheme)
|
||||
digest1 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.color_scheme_digest
|
||||
|
||||
ColorSchemeRevisor.revise(scheme, colors: [{ name: "primary", hex: "CC0000" }])
|
||||
|
||||
digest2 = manager.color_scheme_digest
|
||||
digest2 = builder.color_scheme_digest
|
||||
|
||||
expect(digest1).to_not eq(digest2)
|
||||
end
|
||||
|
||||
it "updates digest when updating a theme's color definitions" do
|
||||
scheme = ColorScheme.base
|
||||
manager = Stylesheet::Manager.new(:color_definitions, theme.id, scheme)
|
||||
digest1 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.color_scheme_digest
|
||||
|
||||
theme.set_field(target: :common, name: :color_definitions, value: 'body {color: brown}')
|
||||
theme.save!
|
||||
|
||||
digest2 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest2 = builder.color_scheme_digest
|
||||
|
||||
expect(digest1).to_not eq(digest2)
|
||||
end
|
||||
|
||||
it "updates digest when updating a theme component's color definitions" do
|
||||
scheme = ColorScheme.base
|
||||
manager = Stylesheet::Manager.new(:color_definitions, theme.id, scheme)
|
||||
digest1 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest1 = builder.color_scheme_digest
|
||||
|
||||
child_theme = Fabricate(:theme, component: true)
|
||||
child_theme.set_field(target: :common, name: "color_definitions", value: 'body {color: fuchsia}')
|
||||
|
@ -272,26 +358,41 @@ describe Stylesheet::Manager do
|
|||
theme.add_relative_theme!(:child, child_theme)
|
||||
theme.save!
|
||||
|
||||
digest2 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest2 = builder.color_scheme_digest
|
||||
expect(digest1).to_not eq(digest2)
|
||||
|
||||
child_theme.set_field(target: :common, name: "color_definitions", value: 'body {color: blue}')
|
||||
child_theme.save!
|
||||
digest3 = manager.color_scheme_digest
|
||||
expect(digest2).to_not eq(digest3)
|
||||
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
digest3 = builder.color_scheme_digest
|
||||
expect(digest2).to_not eq(digest3)
|
||||
end
|
||||
|
||||
it "updates digest when setting fonts" do
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
digest1 = manager.color_scheme_digest
|
||||
manager = manager(theme.id)
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
digest1 = builder.color_scheme_digest
|
||||
SiteSetting.base_font = DiscourseFonts.fonts[2][:key]
|
||||
digest2 = manager.color_scheme_digest
|
||||
digest2 = builder.color_scheme_digest
|
||||
|
||||
expect(digest1).to_not eq(digest2)
|
||||
|
||||
SiteSetting.heading_font = DiscourseFonts.fonts[4][:key]
|
||||
digest3 = manager.color_scheme_digest
|
||||
digest3 = builder.color_scheme_digest
|
||||
|
||||
expect(digest3).to_not eq(digest2)
|
||||
end
|
||||
|
@ -300,23 +401,23 @@ describe Stylesheet::Manager do
|
|||
|
||||
describe 'color_scheme_stylesheets' do
|
||||
it "returns something by default" do
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag()
|
||||
link = manager.color_scheme_stylesheet_link_tag
|
||||
expect(link).not_to eq("")
|
||||
end
|
||||
|
||||
it "does not crash when no default theme is set" do
|
||||
SiteSetting.default_theme_id = -1
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag()
|
||||
link = manager.color_scheme_stylesheet_link_tag
|
||||
expect(link).not_to eq("")
|
||||
end
|
||||
|
||||
it "loads base scheme when defined scheme id is missing" do
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(125)
|
||||
link = manager.color_scheme_stylesheet_link_tag(125)
|
||||
expect(link).to include("color_definitions_base")
|
||||
end
|
||||
|
||||
it "loads nothing when defined dark scheme id is missing" do
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(125, "(prefers-color-scheme: dark)")
|
||||
link = manager.color_scheme_stylesheet_link_tag(125, "(prefers-color-scheme: dark)")
|
||||
expect(link).to eq("")
|
||||
end
|
||||
|
||||
|
@ -325,7 +426,7 @@ describe Stylesheet::Manager do
|
|||
theme = Fabricate(:theme, color_scheme_id: cs.id)
|
||||
SiteSetting.default_theme_id = theme.id
|
||||
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag()
|
||||
link = manager.color_scheme_stylesheet_link_tag()
|
||||
expect(link).to include("/stylesheets/color_definitions_funky_#{cs.id}_")
|
||||
end
|
||||
|
||||
|
@ -337,16 +438,19 @@ describe Stylesheet::Manager do
|
|||
|
||||
user_theme = Fabricate(:theme, color_scheme_id: nil)
|
||||
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(nil, "all", [user_theme.id])
|
||||
link = manager(user_theme.id).color_scheme_stylesheet_link_tag(nil, "all")
|
||||
expect(link).to include("/stylesheets/color_definitions_base_")
|
||||
|
||||
stylesheet = Stylesheet::Manager.new(:color_definitions, user_theme.id, nil).compile(force: true)
|
||||
stylesheet = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: user_theme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(stylesheet).not_to include("--primary: #c00;")
|
||||
expect(stylesheet).to include("--primary: #222;") # from base scheme
|
||||
end
|
||||
|
||||
it "uses the correct scheme when a valid scheme id is used" do
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(ColorScheme.first.id)
|
||||
link = manager.color_scheme_stylesheet_link_tag(ColorScheme.first.id)
|
||||
slug = Slug.for(ColorScheme.first.name) + "_" + ColorScheme.first.id.to_s
|
||||
expect(link).to include("/stylesheets/color_definitions_#{slug}_")
|
||||
end
|
||||
|
@ -356,19 +460,27 @@ describe Stylesheet::Manager do
|
|||
theme = Fabricate(:theme, color_scheme_id: cs.id)
|
||||
SiteSetting.default_theme_id = theme.id
|
||||
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag()
|
||||
link = manager.color_scheme_stylesheet_link_tag
|
||||
expect(link).to include("/stylesheets/color_definitions_funky-bunch_#{cs.id}_")
|
||||
end
|
||||
|
||||
it "updates outputted colors when updating a color scheme" do
|
||||
scheme = ColorScheme.create_from_base(name: "Neutral", base_scheme_id: "Neutral")
|
||||
manager = Stylesheet::Manager.new(:color_definitions, nil, scheme)
|
||||
stylesheet = manager.compile
|
||||
theme = Fabricate(:theme)
|
||||
manager = manager(theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
stylesheet = builder.compile
|
||||
|
||||
ColorSchemeRevisor.revise(scheme, colors: [{ name: "primary", hex: "CC0000" }])
|
||||
|
||||
manager2 = Stylesheet::Manager.new(:color_definitions, nil, scheme)
|
||||
stylesheet2 = manager2.compile
|
||||
builder2 = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
stylesheet2 = builder2.compile
|
||||
|
||||
expect(stylesheet).not_to eq(stylesheet2)
|
||||
expect(stylesheet2).to include("--primary: #c00;")
|
||||
|
@ -389,14 +501,23 @@ describe Stylesheet::Manager do
|
|||
let(:dark_scheme) { ColorScheme.create_from_base(name: 'Dark', base_scheme_id: 'Dark') }
|
||||
|
||||
it "includes theme color definitions in color scheme" do
|
||||
stylesheet = Stylesheet::Manager.new(:color_definitions, theme.id, scheme).compile(force: true)
|
||||
manager = manager(theme.id)
|
||||
|
||||
stylesheet = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(stylesheet).to include("--special: rebeccapurple")
|
||||
end
|
||||
|
||||
it "includes child color definitions in color schemes" do
|
||||
theme.add_relative_theme!(:child, child)
|
||||
theme.save!
|
||||
stylesheet = Stylesheet::Manager.new(:color_definitions, theme.id, scheme).compile(force: true)
|
||||
manager = manager(theme.id)
|
||||
|
||||
stylesheet = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(stylesheet).to include("--special: rebeccapurple")
|
||||
expect(stylesheet).to include("--child-definition: #c00")
|
||||
|
@ -406,7 +527,12 @@ describe Stylesheet::Manager do
|
|||
theme.add_relative_theme!(:child, child)
|
||||
theme.save!
|
||||
|
||||
stylesheet = Stylesheet::Manager.new(:color_definitions, theme.id, dark_scheme).compile(force: true)
|
||||
manager = manager(theme.id)
|
||||
|
||||
stylesheet = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: dark_scheme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(stylesheet).to include("--special: rebeccapurple")
|
||||
expect(stylesheet).to include("--child-definition: #fff")
|
||||
end
|
||||
|
@ -416,7 +542,11 @@ describe Stylesheet::Manager do
|
|||
theme.set_field(target: :common, name: "color_definitions", value: scss)
|
||||
theme.save!
|
||||
|
||||
stylesheet = Stylesheet::Manager.new(:color_definitions, theme.id, scheme)
|
||||
manager = manager(theme.id)
|
||||
|
||||
stylesheet = Stylesheet::Manager::Builder.new(
|
||||
target: :color_definitions, theme: theme, color_scheme: scheme, manager: manager
|
||||
)
|
||||
|
||||
expect { stylesheet.compile }.not_to raise_error
|
||||
end
|
||||
|
@ -432,7 +562,12 @@ describe Stylesheet::Manager do
|
|||
child.set_field(target: :common, name: "scss", value: scss)
|
||||
child.save!
|
||||
|
||||
child_theme_manager = Stylesheet::Manager.new(:desktop_theme, child.id)
|
||||
manager = manager(theme.id)
|
||||
|
||||
child_theme_manager = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: child, manager: manager
|
||||
)
|
||||
|
||||
child_theme_manager.compile(force: true)
|
||||
|
||||
child_css = File.read(child_theme_manager.stylesheet_fullpath)
|
||||
|
@ -448,9 +583,9 @@ describe Stylesheet::Manager do
|
|||
cs = Fabricate(:color_scheme, name: 'Grün')
|
||||
cs2 = Fabricate(:color_scheme, name: '어두운')
|
||||
|
||||
link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(cs.id)
|
||||
link = manager.color_scheme_stylesheet_link_tag(cs.id)
|
||||
expect(link).to include("/stylesheets/color_definitions_grun_#{cs.id}_")
|
||||
link2 = Stylesheet::Manager.color_scheme_stylesheet_link_tag(cs2.id)
|
||||
link2 = manager.color_scheme_stylesheet_link_tag(cs2.id)
|
||||
expect(link2).to include("/stylesheets/color_definitions_scheme_#{cs2.id}_")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,10 @@ describe SvgSprite do
|
|||
it 'can generate paths' do
|
||||
version = SvgSprite.version # Icons won't change for this test
|
||||
expect(SvgSprite.path).to eq("/svg-sprite/#{Discourse.current_hostname}/svg--#{version}.js")
|
||||
expect(SvgSprite.path([1, 2])).to eq("/svg-sprite/#{Discourse.current_hostname}/svg-1,2-#{version}.js")
|
||||
expect(SvgSprite.path(1)).to eq("/svg-sprite/#{Discourse.current_hostname}/svg-1-#{version}.js")
|
||||
|
||||
# Safe mode
|
||||
expect(SvgSprite.path([nil])).to eq("/svg-sprite/#{Discourse.current_hostname}/svg--#{version}.js")
|
||||
expect(SvgSprite.path(nil)).to eq("/svg-sprite/#{Discourse.current_hostname}/svg--#{version}.js")
|
||||
end
|
||||
|
||||
it 'can search for a specific FA icon' do
|
||||
|
@ -54,13 +54,13 @@ describe SvgSprite do
|
|||
fname = "custom-theme-icon-sprite.svg"
|
||||
upload = UploadCreator.new(file_from_fixtures(fname), fname, for_theme: true).create_for(-1)
|
||||
|
||||
version1 = SvgSprite.version([theme.id])
|
||||
bundle1 = SvgSprite.bundle([theme.id])
|
||||
version1 = SvgSprite.version(theme.id)
|
||||
bundle1 = SvgSprite.bundle(theme.id)
|
||||
|
||||
SiteSetting.svg_icon_subset = "my-custom-theme-icon"
|
||||
|
||||
version2 = SvgSprite.version([theme.id])
|
||||
bundle2 = SvgSprite.bundle([theme.id])
|
||||
version2 = SvgSprite.version(theme.id)
|
||||
bundle2 = SvgSprite.bundle(theme.id)
|
||||
|
||||
# The contents of the bundle should not change, because the icon does not actually exist
|
||||
expect(bundle1).to eq(bundle2)
|
||||
|
@ -71,8 +71,8 @@ describe SvgSprite do
|
|||
theme.set_field(target: :common, name: SvgSprite.theme_sprite_variable_name, upload_id: upload.id, type: :theme_upload_var)
|
||||
theme.save!
|
||||
|
||||
version3 = SvgSprite.version([theme.id])
|
||||
bundle3 = SvgSprite.bundle([theme.id])
|
||||
version3 = SvgSprite.version(theme.id)
|
||||
bundle3 = SvgSprite.bundle(theme.id)
|
||||
|
||||
# The version/bundle should be updated
|
||||
expect(bundle3).not_to match(bundle2)
|
||||
|
@ -97,34 +97,34 @@ describe SvgSprite do
|
|||
# Works for default settings:
|
||||
theme.set_field(target: :settings, name: :yaml, value: "custom_icon: dragon")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("dragon")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("dragon")
|
||||
|
||||
# Automatically purges cache when default changes:
|
||||
theme.set_field(target: :settings, name: :yaml, value: "custom_icon: gamepad")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("gamepad")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("gamepad")
|
||||
|
||||
# Works when applying override
|
||||
theme.update_setting(:custom_icon, "gas-pump")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("gas-pump")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("gas-pump")
|
||||
|
||||
# Works when changing override
|
||||
theme.update_setting(:custom_icon, "gamepad")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("gamepad")
|
||||
expect(SvgSprite.all_icons([theme.id])).not_to include("gas-pump")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("gamepad")
|
||||
expect(SvgSprite.all_icons(theme.id)).not_to include("gas-pump")
|
||||
|
||||
# FA5 syntax
|
||||
theme.update_setting(:custom_icon, "fab fa-bandcamp")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("fab-bandcamp")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("fab-bandcamp")
|
||||
|
||||
# Internal Discourse syntax + multiple icons
|
||||
theme.update_setting(:custom_icon, "fab-android|dragon")
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("fab-android")
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("dragon")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("fab-android")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("dragon")
|
||||
|
||||
# Check themes don't leak into non-theme sprite sheet
|
||||
expect(SvgSprite.all_icons).not_to include("dragon")
|
||||
|
@ -134,17 +134,17 @@ describe SvgSprite do
|
|||
theme.save!
|
||||
parent_theme = Fabricate(:theme)
|
||||
parent_theme.add_relative_theme!(:child, theme)
|
||||
expect(SvgSprite.all_icons([parent_theme.id])).to include("dragon")
|
||||
expect(SvgSprite.all_icons(parent_theme.id)).to include("dragon")
|
||||
end
|
||||
|
||||
it 'includes icons defined in theme modifiers' do
|
||||
theme = Fabricate(:theme)
|
||||
|
||||
expect(SvgSprite.all_icons([theme.id])).not_to include("dragon")
|
||||
expect(SvgSprite.all_icons(theme.id)).not_to include("dragon")
|
||||
|
||||
theme.theme_modifier_set.svg_icons = ["dragon"]
|
||||
theme.save!
|
||||
expect(SvgSprite.all_icons([theme.id])).to include("dragon")
|
||||
expect(SvgSprite.all_icons(theme.id)).to include("dragon")
|
||||
end
|
||||
|
||||
it 'includes custom icons from a sprite in a theme' do
|
||||
|
@ -157,7 +157,7 @@ describe SvgSprite do
|
|||
theme.save!
|
||||
|
||||
expect(Upload.where(id: upload.id)).to be_exist
|
||||
expect(SvgSprite.bundle([theme.id])).to match(/my-custom-theme-icon/)
|
||||
expect(SvgSprite.bundle(theme.id)).to match(/my-custom-theme-icon/)
|
||||
end
|
||||
|
||||
context "s3" do
|
||||
|
@ -181,17 +181,17 @@ describe SvgSprite do
|
|||
theme.set_field(target: :common, name: SvgSprite.theme_sprite_variable_name, upload_id: upload_s3.id, type: :theme_upload_var)
|
||||
theme.save!
|
||||
|
||||
sprite_files = SvgSprite.custom_svg_sprites([theme.id]).join("|")
|
||||
sprite_files = SvgSprite.custom_svg_sprites(theme.id).join("|")
|
||||
expect(sprite_files).to match(/#{upload_s3.sha1}/)
|
||||
expect(sprite_files).not_to match(/amazonaws/)
|
||||
|
||||
SvgSprite.bundle([theme.id])
|
||||
SvgSprite.bundle(theme.id)
|
||||
expect(SvgSprite.cache.hash.keys).to include("custom_svg_sprites_#{theme.id}")
|
||||
|
||||
external_copy = Discourse.store.download(upload_s3)
|
||||
File.delete external_copy.try(:path)
|
||||
|
||||
SvgSprite.bundle([theme.id])
|
||||
SvgSprite.bundle(theme.id)
|
||||
# when a file is missing, ensure that cache entry is cleared
|
||||
expect(SvgSprite.cache.hash.keys).to_not include("custom_svg_sprites_#{theme.id}")
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ describe ApplicationHelper do
|
|||
user_id: -1,
|
||||
color_scheme_id: ColorScheme.find_by(base_scheme_id: "Dark").id
|
||||
)
|
||||
helper.request.env[:resolved_theme_ids] = [dark_theme.id]
|
||||
helper.request.env[:resolved_theme_id] = dark_theme.id
|
||||
end
|
||||
context "on desktop" do
|
||||
before do
|
||||
|
@ -509,7 +509,7 @@ describe ApplicationHelper do
|
|||
user_id: -1,
|
||||
color_scheme_id: ColorScheme.find_by(base_scheme_id: "Dark").id
|
||||
)
|
||||
helper.request.env[:resolved_theme_ids] = [dark_theme.id]
|
||||
helper.request.env[:resolved_theme_id] = dark_theme.id
|
||||
|
||||
expect(helper.dark_color_scheme?).to eq(true)
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ describe ThemeModifierHelper do
|
|||
end
|
||||
|
||||
it "can extract theme ids from a request object" do
|
||||
request = Rack::Request.new({ resolved_theme_ids: [theme.id] })
|
||||
request = Rack::Request.new({ resolved_theme_id: theme.id })
|
||||
tmh = ThemeModifierHelper.new(request: request)
|
||||
expect(tmh.serialize_topic_excerpts).to eq(true)
|
||||
end
|
||||
|
|
|
@ -20,13 +20,14 @@ describe ColorScheme do
|
|||
theme.set_field(name: :scss, target: :desktop, value: '.bob {color: $primary;}')
|
||||
theme.save!
|
||||
|
||||
href = Stylesheet::Manager.stylesheet_data(:desktop_theme, theme.id)[0][:new_href]
|
||||
colors_href = Stylesheet::Manager.color_scheme_stylesheet_details(scheme.id, "all", nil)
|
||||
manager = Stylesheet::Manager.new(theme_id: theme.id)
|
||||
href = manager.stylesheet_data(:desktop_theme)[0][:new_href]
|
||||
colors_href = manager.color_scheme_stylesheet_details(scheme.id, "all")
|
||||
|
||||
ColorSchemeRevisor.revise(scheme, colors: [{ name: 'primary', hex: 'bbb' }])
|
||||
|
||||
href2 = Stylesheet::Manager.stylesheet_data(:desktop_theme, theme.id)[0][:new_href]
|
||||
colors_href2 = Stylesheet::Manager.color_scheme_stylesheet_details(scheme.id, "all", nil)
|
||||
href2 = manager.stylesheet_data(:desktop_theme)[0][:new_href]
|
||||
colors_href2 = manager.color_scheme_stylesheet_details(scheme.id, "all")
|
||||
|
||||
expect(href).not_to eq(href2)
|
||||
expect(colors_href).not_to eq(colors_href2)
|
||||
|
|
|
@ -66,24 +66,22 @@ describe Theme do
|
|||
end
|
||||
|
||||
it "can automatically disable for mismatching version" do
|
||||
expect(theme.supported?).to eq(true)
|
||||
theme.create_remote_theme!(remote_url: "", minimum_discourse_version: "99.99.99")
|
||||
theme.save!
|
||||
expect(theme.supported?).to eq(false)
|
||||
|
||||
expect(Theme.transform_ids([theme.id])).to be_empty
|
||||
expect(Theme.transform_ids(theme.id)).to eq([])
|
||||
end
|
||||
|
||||
xit "#transform_ids works with nil values" do
|
||||
it "#transform_ids works with nil values" do
|
||||
# Used in safe mode
|
||||
expect(Theme.transform_ids([nil])).to eq([nil])
|
||||
expect(Theme.transform_ids(nil)).to eq([])
|
||||
end
|
||||
|
||||
it '#transform_ids filters out disabled components' do
|
||||
theme.add_relative_theme!(:child, child)
|
||||
expect(Theme.transform_ids([theme.id], extend: true)).to eq([theme.id, child.id])
|
||||
expect(Theme.transform_ids(theme.id)).to eq([theme.id, child.id])
|
||||
child.update!(enabled: false)
|
||||
expect(Theme.transform_ids([theme.id], extend: true)).to eq([theme.id])
|
||||
expect(Theme.transform_ids(theme.id)).to eq([theme.id])
|
||||
end
|
||||
|
||||
it "doesn't allow multi-level theme components" do
|
||||
|
@ -171,19 +169,6 @@ HTML
|
|||
expect(Theme.lookup_field(theme.id, :desktop, :body_tag)).to match(/<b>test<\/b>/)
|
||||
end
|
||||
|
||||
it 'can find fields for multiple themes' do
|
||||
theme2 = Fabricate(:theme)
|
||||
|
||||
theme.set_field(target: :common, name: :body_tag, value: "<b>testtheme1</b>")
|
||||
theme2.set_field(target: :common, name: :body_tag, value: "<b>theme2test</b>")
|
||||
theme.save!
|
||||
theme2.save!
|
||||
|
||||
field = Theme.lookup_field([theme.id, theme2.id], :desktop, :body_tag)
|
||||
expect(field).to match(/<b>testtheme1<\/b>/)
|
||||
expect(field).to match(/<b>theme2test<\/b>/)
|
||||
end
|
||||
|
||||
describe "#switch_to_component!" do
|
||||
it "correctly converts a theme to component" do
|
||||
theme.add_relative_theme!(:child, child)
|
||||
|
@ -229,25 +214,13 @@ HTML
|
|||
end
|
||||
|
||||
it "returns an empty array if no ids are passed" do
|
||||
expect(Theme.transform_ids([])).to eq([])
|
||||
expect(Theme.transform_ids(nil)).to eq([])
|
||||
end
|
||||
|
||||
it "adds the child themes of the parent" do
|
||||
sorted = [child.id, child2.id].sort
|
||||
|
||||
expect(Theme.transform_ids([theme.id])).to eq([theme.id, *sorted])
|
||||
|
||||
expect(Theme.transform_ids([theme.id, orphan1.id, orphan2.id])).to eq([theme.id, orphan1.id, *sorted, orphan2.id])
|
||||
end
|
||||
|
||||
it "doesn't insert children when extend is false" do
|
||||
fake_id = orphan2.id
|
||||
fake_id2 = orphan3.id
|
||||
fake_id3 = orphan4.id
|
||||
|
||||
expect(Theme.transform_ids([theme.id], extend: false)).to eq([theme.id])
|
||||
expect(Theme.transform_ids([theme.id, fake_id3, fake_id, fake_id2, fake_id2], extend: false))
|
||||
.to eq([theme.id, fake_id, fake_id2, fake_id3])
|
||||
expect(Theme.transform_ids(theme.id)).to eq([theme.id, *sorted])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -317,7 +290,12 @@ HTML
|
|||
theme.reload
|
||||
expect(theme.theme_fields.find_by(name: :scss).error).to eq(nil)
|
||||
|
||||
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
|
||||
manager = Stylesheet::Manager.new(theme_id: theme.id)
|
||||
|
||||
scss, _map = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(scss).to include(upload.url)
|
||||
end
|
||||
end
|
||||
|
@ -328,7 +306,12 @@ HTML
|
|||
theme.set_field(target: :common, name: :scss, value: 'body {background-color: $background_color; font-size: $font-size}')
|
||||
theme.save!
|
||||
|
||||
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
|
||||
manager = Stylesheet::Manager.new(theme_id: theme.id)
|
||||
|
||||
scss, _map = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(scss).to include("background-color:red")
|
||||
expect(scss).to include("font-size:25px")
|
||||
|
||||
|
@ -336,7 +319,10 @@ HTML
|
|||
setting.value = '30px'
|
||||
theme.save!
|
||||
|
||||
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
|
||||
scss, _map = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(scss).to include("font-size:30px")
|
||||
|
||||
# Escapes correctly. If not, compiling this would throw an exception
|
||||
|
@ -348,7 +334,10 @@ HTML
|
|||
theme.set_field(target: :common, name: :scss, value: 'body {font-size: quote($font-size)}')
|
||||
theme.save!
|
||||
|
||||
scss, _map = Stylesheet::Manager.new(:desktop_theme, theme.id).compile(force: true)
|
||||
scss, _map = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
).compile(force: true)
|
||||
|
||||
expect(scss).to include('font-size:"#{$fakeinterpolatedvariable}\a andanothervalue \'withquotes\'; margin: 0;\a"')
|
||||
end
|
||||
|
||||
|
@ -804,8 +793,13 @@ HTML
|
|||
}}
|
||||
|
||||
let(:compiler) {
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
manager.compile(force: true)
|
||||
manager = Stylesheet::Manager.new(theme_id: theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: theme, manager: manager
|
||||
)
|
||||
|
||||
builder.compile(force: true)
|
||||
}
|
||||
|
||||
it "works when importing file by path" do
|
||||
|
@ -829,8 +823,13 @@ HTML
|
|||
child_theme.set_field(target: :common, name: :scss, value: '@import "my_files/moremagic"')
|
||||
child_theme.save!
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, child_theme.id)
|
||||
css, _map = manager.compile(force: true)
|
||||
manager = Stylesheet::Manager.new(theme_id: child_theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(
|
||||
target: :desktop_theme, theme: child_theme, manager: manager
|
||||
)
|
||||
|
||||
css, _map = builder.compile(force: true)
|
||||
expect(css).to include("body{background:green}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -442,13 +442,13 @@ RSpec.describe ApplicationController do
|
|||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme.id])
|
||||
expect(controller.theme_id).to eq(theme.id)
|
||||
|
||||
theme.update_attribute(:user_selectable, false)
|
||||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([SiteSetting.default_theme_id])
|
||||
expect(controller.theme_id).to eq(SiteSetting.default_theme_id)
|
||||
end
|
||||
|
||||
it "can be overridden with a cookie" do
|
||||
|
@ -458,15 +458,7 @@ RSpec.describe ApplicationController do
|
|||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme2.id])
|
||||
|
||||
theme2.update!(user_selectable: false, component: true)
|
||||
theme.add_relative_theme!(:child, theme2)
|
||||
cookies['theme_ids'] = "#{theme.id},#{theme2.id}|#{user.user_option.theme_key_seq}"
|
||||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme.id, theme2.id])
|
||||
expect(controller.theme_id).to eq(theme2.id)
|
||||
end
|
||||
|
||||
it "falls back to the default theme when the user has no cookies or preferences" do
|
||||
|
@ -476,25 +468,25 @@ RSpec.describe ApplicationController do
|
|||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme2.id])
|
||||
expect(controller.theme_id).to eq(theme2.id)
|
||||
end
|
||||
|
||||
it "can be overridden with preview_theme_id param" do
|
||||
sign_in(admin)
|
||||
cookies['theme_ids'] = "#{theme.id},#{theme2.id}|#{admin.user_option.theme_key_seq}"
|
||||
cookies['theme_ids'] = "#{theme.id}|#{admin.user_option.theme_key_seq}"
|
||||
|
||||
get "/", params: { preview_theme_id: theme2.id }
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme2.id])
|
||||
expect(controller.theme_id).to eq(theme2.id)
|
||||
|
||||
get "/", params: { preview_theme_id: non_selectable_theme.id }
|
||||
expect(controller.theme_ids).to eq([non_selectable_theme.id])
|
||||
expect(controller.theme_id).to eq(non_selectable_theme.id)
|
||||
end
|
||||
|
||||
it "does not allow non privileged user to preview themes" do
|
||||
sign_in(user)
|
||||
get "/", params: { preview_theme_id: non_selectable_theme.id }
|
||||
expect(controller.theme_ids).to eq([SiteSetting.default_theme_id])
|
||||
expect(controller.theme_id).to eq(SiteSetting.default_theme_id)
|
||||
end
|
||||
|
||||
it "cookie can fail back to user if out of sync" do
|
||||
|
@ -503,7 +495,7 @@ RSpec.describe ApplicationController do
|
|||
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
expect(controller.theme_ids).to eq([theme.id])
|
||||
expect(controller.theme_id).to eq(theme.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ RSpec.describe SafeModeController do
|
|||
theme.set_default!
|
||||
|
||||
get '/safe-mode'
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).not_to include("My Custom Header")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,8 @@ require 'rails_helper'
|
|||
describe StylesheetsController do
|
||||
it 'can survive cache miss' do
|
||||
StylesheetCache.destroy_all
|
||||
builder = Stylesheet::Manager.new('desktop_rtl', nil)
|
||||
manager = Stylesheet::Manager.new(theme_id: nil)
|
||||
builder = Stylesheet::Manager::Builder.new(target: 'desktop_rtl', manager: manager, theme: nil)
|
||||
builder.compile
|
||||
|
||||
digest = StylesheetCache.first.digest
|
||||
|
@ -31,7 +32,9 @@ describe StylesheetsController do
|
|||
scheme = ColorScheme.create_from_base(name: "testing", colors: [])
|
||||
theme = Fabricate(:theme, color_scheme_id: scheme.id)
|
||||
|
||||
builder = Stylesheet::Manager.new(:desktop, theme.id)
|
||||
manager = Stylesheet::Manager.new(theme_id: theme.id)
|
||||
|
||||
builder = Stylesheet::Manager::Builder.new(target: :desktop, theme: theme, manager: manager)
|
||||
builder.compile
|
||||
|
||||
`rm -rf #{Stylesheet::Manager.cache_fullpath}`
|
||||
|
@ -44,7 +47,7 @@ describe StylesheetsController do
|
|||
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
builder = Stylesheet::Manager.new(:desktop_theme, theme.id)
|
||||
builder = Stylesheet::Manager::Builder.new(target: :desktop_theme, theme: theme, manager: manager)
|
||||
builder.compile
|
||||
|
||||
`rm -rf #{Stylesheet::Manager.cache_fullpath}`
|
||||
|
|
Loading…
Reference in New Issue