mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Native theme support
This feature introduces the concept of themes. Themes are an evolution
of site customizations.
Themes introduce two very big conceptual changes:
- A theme may include other "child themes", children can include grand
children and so on.
- A theme may specify a color scheme
The change does away with the idea of "enabled" color schemes.
It also adds a bunch of big niceties like
- You can source a theme from a git repo
- History for themes is much improved
- You can only have a single enabled theme. Themes can be selected by
users, if you opt for it.
On a technical level this change comes with a whole bunch of goodies
- All CSS is now compiled using a custom pipeline that uses libsass
see /lib/stylesheet
- There is a single pipeline for css compilation (in the past we used
one for customizations and another one for the rest of the app
- The stylesheet pipeline is now divorced of sprockets, there is no
reliance on sprockets for CSS bundling
- CSS is generated with source maps everywhere (including themes) this
makes debugging much easier
- Our "live reloader" is smarter and avoid a flash of unstyled content
we run a file watcher in "puma" in dev so you no longer need to run
rake autospec to watch for CSS changes
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
require 'rails_helper'
|
||||
require_dependency 'sass/discourse_sass_compiler'
|
||||
|
||||
describe DiscourseSassCompiler do
|
||||
|
||||
let(:test_scss) { "body { p {color: blue;} }\n@import 'common/foundation/variables';\n@import 'plugins';" }
|
||||
|
||||
describe '#compile' do
|
||||
it "compiles scss" do
|
||||
DiscoursePluginRegistry.stubs(:stylesheets).returns(["#{Rails.root}/spec/fixtures/scss/my_plugin.scss"])
|
||||
css = described_class.compile(test_scss, "test")
|
||||
expect(css).to include("color")
|
||||
expect(css).to include('my-plugin-thing')
|
||||
end
|
||||
|
||||
it "raises error for invalid scss" do
|
||||
expect {
|
||||
described_class.compile("this isn't valid scss", "test")
|
||||
}.to raise_error(Sass::SyntaxError)
|
||||
end
|
||||
|
||||
it "doesn't load theme or plugins in safe mode" do
|
||||
ColorScheme.expects(:enabled).never
|
||||
DiscoursePluginRegistry.stubs(:stylesheets).returns(["#{Rails.root}/spec/fixtures/scss/my_plugin.scss"])
|
||||
css = described_class.compile(test_scss, "test", safe: true)
|
||||
expect(css).not_to include('my-plugin-thing')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
require 'rails_helper'
|
||||
require_dependency 'sass/discourse_stylesheets'
|
||||
|
||||
describe DiscourseStylesheets do
|
||||
|
||||
describe "compile" do
|
||||
it "can compile desktop bundle" do
|
||||
DiscoursePluginRegistry.stubs(:stylesheets).returns(["#{Rails.root}/spec/fixtures/scss/my_plugin.scss"])
|
||||
builder = described_class.new(:desktop)
|
||||
expect(builder.compile(force: true)).to include('my-plugin-thing')
|
||||
FileUtils.rm builder.stylesheet_fullpath
|
||||
end
|
||||
|
||||
it "can compile mobile bundle" do
|
||||
DiscoursePluginRegistry.stubs(:mobile_stylesheets).returns(["#{Rails.root}/spec/fixtures/scss/my_plugin.scss"])
|
||||
builder = described_class.new(:mobile)
|
||||
expect(builder.compile(force: true)).to include('my-plugin-thing')
|
||||
FileUtils.rm builder.stylesheet_fullpath
|
||||
end
|
||||
|
||||
it "can fallback when css is bad" do
|
||||
DiscoursePluginRegistry.stubs(:stylesheets).returns([
|
||||
"#{Rails.root}/spec/fixtures/scss/my_plugin.scss",
|
||||
"#{Rails.root}/spec/fixtures/scss/broken.scss"
|
||||
])
|
||||
builder = described_class.new(:desktop)
|
||||
expect(builder.compile(force: true)).not_to include('my-plugin-thing')
|
||||
FileUtils.rm builder.stylesheet_fullpath
|
||||
end
|
||||
end
|
||||
|
||||
describe "#digest" do
|
||||
before do
|
||||
described_class.expects(:max_file_mtime).returns(Time.new(2016, 06, 05, 12, 30, 0, 0))
|
||||
end
|
||||
|
||||
it "should return a digest" do
|
||||
expect(described_class.new.digest).to eq('0e6c2e957cfc92ed60661c90ec3345198ccef887')
|
||||
end
|
||||
|
||||
it "should include the cdn url when generating the digest" do
|
||||
GlobalSetting.expects(:cdn_url).returns('https://fastly.maxcdn.org')
|
||||
expect(described_class.new.digest).to eq('4995163b1232c54c8ed3b44200d803a90bc47613')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -151,27 +151,31 @@ describe Wizard::StepUpdater do
|
||||
let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) }
|
||||
|
||||
it "updates the scheme" do
|
||||
updater = wizard.create_updater('colors', theme_id: 'dark')
|
||||
updater = wizard.create_updater('colors', base_scheme_id: 'dark')
|
||||
updater.update
|
||||
expect(updater.success?).to eq(true)
|
||||
expect(wizard.completed_steps?('colors')).to eq(true)
|
||||
|
||||
color_scheme.reload
|
||||
expect(color_scheme).to be_enabled
|
||||
|
||||
theme = Theme.find_by(key: SiteSetting.default_theme_key)
|
||||
expect(theme.color_scheme_id).to eq(color_scheme.id)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context "without an existing scheme" do
|
||||
it "creates the scheme" do
|
||||
updater = wizard.create_updater('colors', theme_id: 'dark')
|
||||
updater = wizard.create_updater('colors', base_scheme_id: 'dark')
|
||||
updater.update
|
||||
expect(updater.success?).to eq(true)
|
||||
expect(wizard.completed_steps?('colors')).to eq(true)
|
||||
|
||||
color_scheme = ColorScheme.where(via_wizard: true).first
|
||||
expect(color_scheme).to be_present
|
||||
expect(color_scheme).to be_enabled
|
||||
expect(color_scheme.colors).to be_present
|
||||
|
||||
theme = Theme.find_by(key: SiteSetting.default_theme_key)
|
||||
expect(theme.color_scheme_id).to eq(color_scheme.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
21
spec/components/stylesheet/compiler_spec.rb
Normal file
21
spec/components/stylesheet/compiler_spec.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
require 'rails_helper'
|
||||
require 'stylesheet/compiler'
|
||||
|
||||
describe Stylesheet::Compiler do
|
||||
it "can compile desktop mobile and desktop css" do
|
||||
css,_map = Stylesheet::Compiler.compile_asset("desktop")
|
||||
expect(css.length).to be > 1000
|
||||
|
||||
css,_map = Stylesheet::Compiler.compile_asset("mobile")
|
||||
expect(css.length).to be > 1000
|
||||
end
|
||||
|
||||
it "supports asset-url" do
|
||||
css,_map = Stylesheet::Compiler.compile(".body{background-image: asset-url('foo.png');}","test.scss")
|
||||
|
||||
expect(css).to include("url('/foo.png')")
|
||||
expect(css).not_to include('asset-url')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
57
spec/components/stylesheet/manager_spec.rb
Normal file
57
spec/components/stylesheet/manager_spec.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
require 'rails_helper'
|
||||
require 'stylesheet/compiler'
|
||||
|
||||
describe Stylesheet::Manager do
|
||||
it 'can correctly compile theme css' do
|
||||
theme = Theme.new(
|
||||
name: 'parent',
|
||||
user_id: -1
|
||||
)
|
||||
|
||||
theme.set_field(:common, "scss", ".common{.scss{color: red;}}")
|
||||
theme.set_field(:desktop, "scss", ".desktop{.scss{color: red;}}")
|
||||
theme.set_field(:mobile, "scss", ".mobile{.scss{color: red;}}")
|
||||
theme.set_field(:common, "embedded_scss", ".embedded{.scss{color: red;}}")
|
||||
|
||||
theme.save!
|
||||
|
||||
|
||||
child_theme = Theme.new(
|
||||
name: 'parent',
|
||||
user_id: -1,
|
||||
)
|
||||
|
||||
child_theme.set_field(:common, "scss", ".child_common{.scss{color: red;}}")
|
||||
child_theme.set_field(:desktop, "scss", ".child_desktop{.scss{color: red;}}")
|
||||
child_theme.set_field(:mobile, "scss", ".child_mobile{.scss{color: red;}}")
|
||||
child_theme.set_field(:common, "embedded_scss", ".child_embedded{.scss{color: red;}}")
|
||||
child_theme.save!
|
||||
|
||||
theme.add_child_theme!(child_theme)
|
||||
|
||||
old_link = Stylesheet::Manager.stylesheet_link_tag(:desktop_theme, 'all', theme.key)
|
||||
|
||||
manager = Stylesheet::Manager.new(:desktop_theme, theme.key)
|
||||
manager.compile(force: true)
|
||||
|
||||
css = File.read(manager.stylesheet_fullpath)
|
||||
_source_map = File.read(manager.source_map_fullpath)
|
||||
|
||||
expect(css).to match(/child_common/)
|
||||
expect(css).to match(/child_desktop/)
|
||||
expect(css).to match(/\.common/)
|
||||
expect(css).to match(/\.desktop/)
|
||||
|
||||
|
||||
child_theme.set_field(:desktop, :scss, ".nothing{color: green;}")
|
||||
child_theme.save!
|
||||
|
||||
new_link = Stylesheet::Manager.stylesheet_link_tag(:desktop_theme, 'all', theme.key)
|
||||
|
||||
expect(new_link).not_to eq(old_link)
|
||||
|
||||
# our theme better have a name with the theme_id as part of it
|
||||
expect(new_link).to include("/stylesheets/desktop_theme_#{theme.id}_")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user