discourse/lib/stylesheet/importer.rb
Martin Brennan 3135f472e2
FEATURE: Improve wizard quality and rearrange steps (#30055)
This commit contains various quality improvements to
our site setup wizard, along with some rearrangement of
steps to improve the admin setup experience and encourage
admins to customize the site early to avoid "all sites look the
same" sentiment.

#### Step rearrangement

* “Your site is ready” from 3 → 4
* “Logos” from 4 → 5
* “Look and feel” from 5 → 3

#### Font selector improvements

Changes the wizard font selector dropdown to show
a preview of all fonts with a CSS class so you don't
have to choose the font to get a preview.

Also makes the fonts appear in alphabetical order.

#### Preview improvements

Placeholder text changed from lorem ipsum to actual topic titles,
category names, and post content. This makes it feel more "real".

Fixes "undefined" categories. Added a date to the topic timeline.

Fixes button rectangles and other UI elements not changing in
size when the font changed, leading to cut off text which looked super
messy. Also fixed some font color issues.

Fixed table header alignment for Latest topic list.

#### Homepage style selector improvements

Limited the big list of homepage styles to Latest, Hot, Categories with latest topics,
and Category boxes based on research into the most common options.

#### Preview header

Changed the preview header to move the hamburger to the left
and add a chat icon

#### And more!

Changed the background of the wizard to use our branded blob style.
2025-01-02 09:28:23 +10:00

237 lines
6.7 KiB
Ruby

# frozen_string_literal: true
require "global_path"
module Stylesheet
class Importer
include GlobalPath
THEME_TARGETS = %w[embedded_theme mobile_theme desktop_theme]
def self.plugin_assets
@plugin_assets ||= {}
end
def self.register_imports!
Discourse.plugins.each do |plugin|
plugin_directory_name = plugin.directory_name
["", "mobile", "desktop"].each do |type|
asset_name = type.present? ? "#{plugin_directory_name}_#{type}" : plugin_directory_name
stylesheets =
(
if type.present?
DiscoursePluginRegistry.send("#{type}_stylesheets")
else
DiscoursePluginRegistry.stylesheets
end
)
plugin_assets[asset_name] = stylesheets[
plugin_directory_name
] if plugin_directory_name.present?
end
end
end
register_imports!
def font
body_font = DiscourseFonts.fonts.find { |f| f[:key] == SiteSetting.base_font }
heading_font = DiscourseFonts.fonts.find { |f| f[:key] == SiteSetting.heading_font }
contents = +""
contents << <<~CSS if body_font.present?
#{font_css(body_font)}
:root {
--font-family: #{body_font[:stack]};
}
CSS
contents << <<~CSS if heading_font.present?
#{font_css(heading_font)}
:root {
--heading-font-family: #{heading_font[:stack]};
}
CSS
contents
end
def wizard_fonts
contents = +""
DiscourseFonts.fonts.each do |font|
if font[:key] == "system"
# Overwrite font definition because the preview canvases in the wizard require explicit @font-face definitions.
# uses same technique as https://github.com/jonathantneal/system-font-css
font[:variants] = [
{
src:
'local(".SFNS-Regular"), local(".SFNSText-Regular"), local(".HelveticaNeueDeskInterface-Regular"), local(".LucidaGrandeUI"), local("Segoe UI"), local("Ubuntu"), local("Roboto-Regular"), local("DroidSans"), local("Tahoma")',
weight: 400,
},
{
src:
'local(".SFNS-Bold"), local(".SFNSText-Bold"), local(".HelveticaNeueDeskInterface-Bold"), local(".LucidaGrandeUI"), local("Segoe UI Bold"), local("Ubuntu Bold"), local("Roboto-Bold"), local("DroidSans-Bold"), local("Tahoma Bold")',
weight: 700,
},
]
end
contents << font_css(font)
contents << <<~CSS
.body-font-#{font[:key].tr("_", "-")} {
font-family: #{font[:stack]};
}
.heading-font-#{font[:key].tr("_", "-")} {
font-family: #{font[:stack]};
}
CSS
end
contents
end
def import_color_definitions
contents = +""
DiscoursePluginRegistry.color_definition_stylesheets.each do |name, path|
contents << "\n\n// Color definitions from #{name}\n\n"
contents << File.read(path.to_s)
contents << "\n\n"
end
theme_id = @theme_id || SiteSetting.default_theme_id
resolved_ids = Theme.transform_ids(theme_id)
if resolved_ids
theme = Theme.find_by_id(theme_id)
contents << theme&.scss_variables.to_s
Theme
.list_baked_fields(resolved_ids, :common, :color_definitions)
.each do |field|
contents << "\n\n// Color definitions from #{field.theme.name}\n\n"
if field.theme_id == theme.id
contents << field.value
else
contents << field.compiled_css(prepended_scss)
end
contents << "\n\n"
end
end
contents
end
def import_wcag_overrides
if @color_scheme_id && ColorScheme.find_by_id(@color_scheme_id)&.is_wcag?
return "@import \"wcag\";"
end
""
end
def color_variables
contents = +""
if @color_scheme_id
colors =
begin
ColorScheme.find(@color_scheme_id).resolved_colors
rescue StandardError
ColorScheme.base_colors
end
elsif (@theme_id && !theme.component)
colors = theme&.color_scheme&.resolved_colors || ColorScheme.base_colors
else
# this is a slightly ugly backwards compatibility fix,
# we shouldn't be using the default theme color scheme for components
# (most components use CSS custom properties which work fine without this)
colors =
Theme.find_by_id(SiteSetting.default_theme_id)&.color_scheme&.resolved_colors ||
ColorScheme.base_colors
end
colors.each { |n, hex| contents << "$#{n}: ##{hex} !default; " }
contents
end
def public_image_path
image_path = UrlHelper.absolute("#{Discourse.base_path}/images")
"$public_image_path: \"#{image_path}\"; "
end
def prepended_scss
"#{color_variables} #{public_image_path} @import \"common/foundation/variables\"; @import \"common/foundation/mixins\"; "
end
def initialize(options)
@theme = options[:theme]
@theme_id = options[:theme_id]
@color_scheme_id = options[:color_scheme_id]
if @theme && !@theme_id
# make up an id so other stuff does not bail out
@theme_id = @theme.id || -1
end
end
def theme_import(target)
return "" if !@theme_id
attr = target == :embedded_theme ? :embedded_scss : :scss
target = target.to_s.gsub("_theme", "").to_sym
contents = +""
fields = theme.list_baked_fields(target, attr)
fields.map do |field|
value = field.value
if value.present?
contents << <<~SCSS
// Theme: #{field.theme.name}
// Target: #{field.target_name} #{field.name}
// Last Edited: #{field.updated_at}
SCSS
contents << value
end
end
contents
end
def theme
@theme = (@theme_id && Theme.find(@theme_id)) || :nil unless @theme
@theme == :nil ? nil : @theme
end
def font_css(font)
contents = +""
if font[:variants].present?
fonts_dir = UrlHelper.absolute("#{Discourse.base_path}/fonts")
font[:variants].each do |variant|
src =
(
if variant[:src]
variant[:src]
else
"url(\"#{fonts_dir}/#{variant[:filename]}?v=#{DiscourseFonts::VERSION}\") format(\"#{variant[:format]}\")"
end
)
contents << <<~CSS
@font-face {
font-family: '#{font[:name]}';
src: #{src};
font-weight: #{variant[:weight]};
}
CSS
end
end
contents
end
end
end