Merge pull request #2217 from vikhyat/css-import

Allow importing Discourse styles in custom CSS
This commit is contained in:
Régis Hanol 2014-04-01 20:14:09 +02:00
commit 8032864efe
61 changed files with 136 additions and 178 deletions

View File

@ -1,17 +0,0 @@
// Manifest
//
//= require ./vendor/normalize
//= require ./common/foundation/base
//= require ./vendor/font_awesome/font-awesome
//= require ./vendor/chosen
//= require_tree ./common/components
//= require ./common/foundation/helpers
//= require_tree ./common
<%
# TODO this is very tricky, we want to add a dependency here on files that may not yet exist
# otherwise in dev we are often stuck nuking the tmp/cache directory
DiscoursePluginRegistry.stylesheets.each do |css|
require_asset(css)
end
%>

View File

@ -0,0 +1,10 @@
@import "vendor/normalize";
@import "common/foundation/base";
@import "vendor/font_awesome/font-awesome";
@import "vendor/chosen";
@import "common/foundation/helpers";
@import "common/foundation/mixins";
@import "common/foundation/variables";
@import "common/components/*";
@import "common/admin/*";
@import "common/input_tip";

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Badges // Badges
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Buttons // Buttons
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,5 +1,3 @@
@import "../foundation/variables";
.topic-list-item td:first-child, .topic-post { .topic-list-item td:first-child, .topic-post {
background-color: inherit; background-color: inherit;
border-left: 1px solid transparent; border-left: 1px solid transparent;

View File

@ -1,7 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
@import "common/foundation/helpers";
// -------------------------------------------------- // --------------------------------------------------
// Navigation menus // Navigation menus
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,5 +0,0 @@
// Manifest
//
//= require common
//= require_tree ./desktop

View File

@ -0,0 +1,2 @@
@import "common";
@import "desktop/*";

View File

@ -1,9 +1,6 @@
// Styles used before the user is logged into discourse. For example, activating their // Styles used before the user is logged into discourse. For example, activating their
// account or changing their email. // account or changing their email.
@import "common/foundation/variables";
@import "common/foundation/mixins";
#simple-container { #simple-container {
@include border-radius-all(10px); @include border-radius-all(10px);
background-color: $primary_background_color; background-color: $primary_background_color;

View File

@ -1,8 +1,5 @@
// styles for the category badge color picker // styles for the category badge color picker
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.category-color-editor { .category-color-editor {
input { input {
width: 70px; width: 70px;

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
.category-combobox { .category-combobox {
.badge-category { .badge-category {
display: inline-block; display: inline-block;

View File

@ -1,8 +1,5 @@
// styles that apply to the reply pane that slides up to compose replies // styles that apply to the reply pane that slides up to compose replies
@import "common/foundation/variables";
@import "common/foundation/mixins";
// hack, this needs to be done cleaner // hack, this needs to be done cleaner
#private-message-users { #private-message-users {
width: 400px; width: 400px;

View File

@ -2,11 +2,6 @@
// BEWARE: changing these styles implies they take effect anywhere they are seen // BEWARE: changing these styles implies they take effect anywhere they are seen
// throughout the Discourse application // throughout the Discourse application
@import "common/foundation/variables";
@import "common/foundation/mixins";
@import "common/foundation/helpers";
html {font-size: 14px/19px;} html {font-size: 14px/19px;}
body { body {

View File

@ -1,7 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
@import "common/foundation/helpers";
// -------------------------------------------------- // --------------------------------------------------
// FAQs // FAQs
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Discourse header // Discourse header
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,8 +1,5 @@
// styles that apply to the popup that appears when you show the edit history of a post // styles that apply to the popup that appears when you show the edit history of a post
@import "common/foundation/variables";
@import "common/foundation/mixins";
.modal.history-modal { .modal.history-modal {
.modal-inner-container { .modal-inner-container {
min-width: 960px; min-width: 960px;

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
.lightbox { .lightbox {
position: relative; position: relative;
display: inline-block; display: inline-block;

View File

@ -1,8 +1,5 @@
// style that apply to the login popup // style that apply to the login popup
@import "common/foundation/variables";
@import "common/foundation/mixins";
#login-buttons { #login-buttons {
button { button {
margin: 0 5px 5px 0; margin: 0 5px 5px 0;

View File

@ -1,8 +1,5 @@
// base styles for every modal popup used in Discourse // base styles for every modal popup used in Discourse
@import "common/foundation/variables";
@import "common/foundation/mixins";
.modal-open { .modal-open {
.dropdown-menu { .dropdown-menu {
z-index: 2050; z-index: 2050;

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
a.loading-onebox { a.loading-onebox {
background: { background: {
image: image-url("spinner_96.gif"); image: image-url("spinner_96.gif");

View File

@ -1,8 +1,5 @@
// styles that apply to the PageDown editor // styles that apply to the PageDown editor
// http://code.google.com/p/pagedown/ // http://code.google.com/p/pagedown/
@import "common/foundation/variables";
@import "common/foundation/mixins";
.wmd-panel { .wmd-panel {
margin-left: 25%; margin-left: 25%;

View File

@ -1,8 +1,5 @@
// styles that apply to the "share" popup when sharing a link to a post or topic // styles that apply to the "share" popup when sharing a link to a post or topic
@import "common/foundation/variables";
@import "common/foundation/mixins";
#poster-expansion { #poster-expansion {
position: absolute; position: absolute;
width: 460px; width: 460px;

View File

@ -1,8 +1,5 @@
// styles that apply to the "share" popup when sharing a link to a post or topic // styles that apply to the "share" popup when sharing a link to a post or topic
@import "common/foundation/variables";
@import "common/foundation/mixins";
#share-link { #share-link {
position: absolute; position: absolute;
left: 20px; left: 20px;

View File

@ -1,8 +1,5 @@
// Styles for the topic admin menu // Styles for the topic admin menu
@import "common/foundation/variables";
@import "common/foundation/mixins";
#show-topic-admin { #show-topic-admin {
position: fixed; position: fixed;
top: 70px; top: 70px;

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Topic lists // Topic lists
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,7 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
@import "common/foundation/helpers";
.gap { .gap {
background-color: lighten($secondary_background_color, 76%); background-color: lighten($secondary_background_color, 76%);
padding: 5px 0; padding: 5px 0;

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
.post-info a { .post-info a {
color: lighten($primary_text_color, 50%); color: lighten($primary_text_color, 50%);
padding-right: 5px; padding-right: 5px;

View File

@ -1,6 +1,3 @@
@import "common/foundation/variables";
@import "common/foundation/mixins";
.add-upload .fa-plus { .add-upload .fa-plus {
font-size: 10px; font-size: 10px;
position: relative; position: relative;

View File

@ -1,6 +1,4 @@
// styles that apply to the user page // styles that apply to the user page
@import "common/foundation/variables";
@import "common/foundation/mixins";
.groups { .groups {
.group-link { .group-link {

View File

@ -1,4 +0,0 @@
// Manifest
//
//= require common
//= require_tree ./mobile

View File

@ -0,0 +1,2 @@
@import "common";
@import "mobile/*";

View File

@ -1,9 +1,6 @@
// Styles used before the user is logged into discourse. For example, activating their // Styles used before the user is logged into discourse. For example, activating their
// account or changing their email. // account or changing their email.
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
#simple-container { #simple-container {
@include border-radius-all(10px); @include border-radius-all(10px);
background-color: $primary_background_color; background-color: $primary_background_color;

View File

@ -1,8 +1,5 @@
// styles for the category badge color picker // styles for the category badge color picker
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.category-color-editor { .category-color-editor {
input { input {
width: 70px; width: 70px;

View File

@ -1,8 +1,5 @@
// styles that apply to the reply pane that slides up to compose replies // styles that apply to the reply pane that slides up to compose replies
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
// hack, this needs to be done cleaner // hack, this needs to be done cleaner
.private-message input.span8 { .private-message input.span8 {
width: 47%; width: 47%;

View File

@ -2,10 +2,6 @@
// BEWARE: changing these styles implies they take effect anywhere they are seen // BEWARE: changing these styles implies they take effect anywhere they are seen
// throughout the Discourse application // throughout the Discourse application
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
@import "../common/foundation/helpers";
body { body {
background-color: $primary_background_color; background-color: $primary_background_color;
} }

View File

@ -1,7 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
@import "../common/foundation/helpers";
// -------------------------------------------------- // --------------------------------------------------
// FAQs // FAQs
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Discourse header // Discourse header
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,8 +1,5 @@
// styles that apply to the popup that appears when you show the edit history of a post // styles that apply to the popup that appears when you show the edit history of a post
@import "common/foundation/variables";
@import "common/foundation/mixins";
.modal.history-modal { .modal.history-modal {
.modal-inner-container { .modal-inner-container {
min-width: 960px; min-width: 960px;

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.lightbox { .lightbox {
position: relative; position: relative;
display: inline-block; display: inline-block;

View File

@ -1,8 +1,5 @@
// style that apply to the login popup // style that apply to the login popup
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.btn-social { .btn-social {
width: 250px; width: 250px;
font-size: 16px; font-size: 16px;

View File

@ -1,8 +1,5 @@
// base styles for every modal popup used in Discourse // base styles for every modal popup used in Discourse
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.modal-open { .modal-open {
.dropdown-menu { .dropdown-menu {
z-index: 2050; z-index: 2050;

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
a.loading-onebox { a.loading-onebox {
background: { background: {
image: image-url("spinner_96.gif"); image: image-url("spinner_96.gif");

View File

@ -1,8 +1,6 @@
// styles that apply to the PageDown editor // styles that apply to the PageDown editor
// http://code.google.com/p/pagedown/ // http://code.google.com/p/pagedown/
@import "../common/foundation/mixins";
.wmd-panel { .wmd-panel {
margin-left: 25%; margin-left: 25%;
margin-right: 25%; margin-right: 25%;

View File

@ -1,8 +1,5 @@
// styles that apply to the "share" popup when sharing a link to a post or topic // styles that apply to the "share" popup when sharing a link to a post or topic
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
#share-link { #share-link {
position: absolute; position: absolute;
left: 20px; left: 20px;

View File

@ -1,8 +1,5 @@
// Styles for the topic admin menu // Styles for the topic admin menu
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
#show-topic-admin { #show-topic-admin {
position: fixed; position: fixed;
top: 70px; top: 70px;

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
// -------------------------------------------------- // --------------------------------------------------
// Topic lists // Topic lists
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,5 +1,3 @@
@import "common/foundation/variables";
.gap { .gap {
background-color: lighten($secondary_background_color, 76%); background-color: lighten($secondary_background_color, 76%);
padding: 5px 15px; padding: 5px 15px;

View File

@ -1,5 +1,3 @@
@import "common/foundation/variables";
.topic-meta-data { .topic-meta-data {
width: 100%; width: 100%;
h3 a {margin-left: 10px;} h3 a {margin-left: 10px;}

View File

@ -1,6 +1,3 @@
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.add-upload .fa-plus { .add-upload .fa-plus {
font-size: 10px; font-size: 10px;
position: relative; position: relative;

View File

@ -1,6 +1,4 @@
// styles that apply to the user page // styles that apply to the user page
@import "../common/foundation/variables";
@import "../common/foundation/mixins";
.user-preferences { .user-preferences {
.control-group { .control-group {

View File

@ -0,0 +1,7 @@
<%
# TODO this is very tricky, we want to add a dependency here on files that may not yet exist
# otherwise in dev we are often stuck nuking the tmp/cache directory
DiscoursePluginRegistry.stylesheets.each do |css|
require_asset(css)
end
%>

View File

@ -60,7 +60,7 @@
width: 12px; width: 12px;
height: 13px; height: 13px;
font-size: 1px; font-size: 1px;
background: url(<%=asset_path "chosen-sprite.png"%>) right top no-repeat; background: asset-url("chosen-sprite.png") right top no-repeat;
} }
.chzn-container-single .chzn-single abbr:hover { .chzn-container-single .chzn-single abbr:hover {
background-position: right -11px; background-position: right -11px;
@ -74,7 +74,7 @@
width: 18px; width: 18px;
} }
.chzn-container-single .chzn-single div b { .chzn-container-single .chzn-single div b {
background: url(<%=asset_path "chosen-sprite.png"%>) no-repeat 0 0; background: asset-url("chosen-sprite.png") no-repeat 0 0;
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -184,7 +184,7 @@
width: 12px; width: 12px;
height: 13px; height: 13px;
font-size: 1px; font-size: 1px;
background: url(<%=asset_path "chosen-sprite.png"%>) right top no-repeat; background: asset-url("chosen-sprite.png") right top no-repeat;
} }
.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover { .chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover {
background-position: right -11px; background-position: right -11px;
@ -264,10 +264,10 @@
bottom: 0; bottom: 0;
} }
.chzn-container .chzn-results-scroll-down span { .chzn-container .chzn-results-scroll-down span {
background: url(<%=asset_path "chosen-sprite.png"%>) no-repeat -4px -3px; background: asset-url("chosen-sprite.png") no-repeat -4px -3px;
} }
.chzn-container .chzn-results-scroll-up span { .chzn-container .chzn-results-scroll-up span {
background: url(<%=asset_path "chosen-sprite.png"%>) no-repeat -22px -3px; background: asset-url("chosen-sprite.png") no-repeat -22px -3px;
} }
/* @end */ /* @end */

View File

@ -1,3 +1,5 @@
require_dependency 'discourse_sass_importer'
class SiteCustomization < ActiveRecord::Base class SiteCustomization < ActiveRecord::Base
ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd' ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd'
# placing this in uploads to ease deployment rules # placing this in uploads to ease deployment rules
@ -11,11 +13,21 @@ class SiteCustomization < ActiveRecord::Base
true true
end end
def compile_stylesheet(scss)
::Sass::Engine.new(scss, {
syntax: :scss,
cache: false,
read_cache: false,
style: :compressed,
filesystem_importer: DiscourseSassImporter
}).render
end
before_save do before_save do
['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr| ['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr|
if self.send("#{stylesheet_attr}_changed?") if self.send("#{stylesheet_attr}_changed?")
begin begin
self.send("#{stylesheet_attr}_baked=", Sass.compile(self.send(stylesheet_attr))) self.send("#{stylesheet_attr}_baked=", compile_stylesheet(self.send(stylesheet_attr)))
rescue Sass::SyntaxError => e rescue Sass::SyntaxError => e
error = e.sass_backtrace_str("custom stylesheet") error = e.sass_backtrace_str("custom stylesheet")
error.gsub!("\n", '\A ') error.gsub!("\n", '\A ')

View File

@ -6,6 +6,8 @@
<% end %> <% end %>
<%- end %> <%- end %>
<%= stylesheet_link_tag "plugins" %>
<%- if staff? %> <%- if staff? %>
<%= stylesheet_link_tag "admin"%> <%= stylesheet_link_tag "admin"%>
<%- end %> <%- end %>

View File

@ -0,0 +1,71 @@
# This custom importer is used for site customizations. This is similar to the
# Sprockets::SassImporter implementation provided in sass-rails since that is used
# during asset precompilation.
class DiscourseSassImporter < Sass::Importers::Filesystem
GLOB = /\*|\[.+\]/
def initialize(*args)
@root = Rails.root.join('app', 'assets', 'stylesheets').to_s
@same_name_warnings = Set.new
end
def extensions
{
'css' => :scss,
'css.scss' => :scss,
'css.sass' => :sass,
'css.erb' => :scss,
'scss.erb' => :scss,
'sass.erb' => :sass,
'css.scss.erb' => :scss,
'css.sass.erb' => :sass
}.merge!(super)
end
def find_relative(name, base, options)
if name =~ GLOB
glob_imports(name, Pathname.new(base), options)
else
engine_from_path(name, File.dirname(base), options)
end
end
def find(name, options)
if name =~ GLOB
nil # globs must be relative
else
engine_from_path(name, root, options)
end
end
def each_globbed_file(glob, base_pathname, options)
Dir["#{base_pathname}/#{glob}"].sort.each do |filename|
next if filename == options[:filename]
yield filename # assume all matching files are requirable
end
end
def glob_imports(glob, base_pathname, options)
contents = ""
each_globbed_file(glob, base_pathname.dirname, options) do |filename|
unless File.directory?(filename)
contents << "@import #{Pathname.new(filename).relative_path_from(base_pathname.dirname).to_s.inspect};\n"
end
end
return nil if contents.empty?
Sass::Engine.new(contents, options.merge(
filename: base_pathname.to_s,
importer: self,
syntax: :scss
))
end
private
def engine_from_path(name, dir, options)
full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
return unless full_filename && File.readable?(full_filename)
Sass::Engine.for_file(full_filename, options)
end
end

View File

@ -16,6 +16,9 @@ task 'assets:precompile:before' do
# let's make precompile faster using redis magic # let's make precompile faster using redis magic
require 'sprockets' require 'sprockets'
require 'digest/sha1' require 'digest/sha1'
require_dependency 'discourse_sass_importer'
Sprockets::SassImporter = DiscourseSassImporter
module ::Sprockets module ::Sprockets
@ -35,10 +38,10 @@ task 'assets:precompile:before' do
def evaluate(context, locals, &block) def evaluate(context, locals, &block)
::Sprockets.cache_compiled("sass", data) do ::Sprockets.cache_compiled("sass", data) do
# HACK, SASS compiler will degrade to aweful perf with huge files # HACK, SASS compiler will degrade to aweful perf with huge files
# Bypass if larger than 200kb, ensure assets are minified prior # Bypass if larger than 500kb, ensure assets are minified prior
if context.pathname && if context.pathname &&
context.pathname.to_s =~ /.css$/ && context.pathname.to_s =~ /.css$/ &&
data.length > 200.kilobytes data.length > 500.kilobytes
puts "Skipped minifying #{context.pathname} cause it is larger than 200KB, minify in source control or avoid large CSS files" puts "Skipped minifying #{context.pathname} cause it is larger than 200KB, minify in source control or avoid large CSS files"
data data
else else

View File

@ -155,12 +155,20 @@ describe SiteCustomization do
it 'should compile scss' do it 'should compile scss' do
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '$black: #000; #a { color: $black; }', header: '') c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '$black: #000; #a { color: $black; }', header: '')
c.stylesheet_baked.should == "#a {\n color: black; }\n" c.stylesheet_baked.should == "#a{color:#000}\n"
end end
it 'should compile mobile scss' do it 'should compile mobile scss' do
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '', header: '', mobile_stylesheet: '$black: #000; #a { color: $black; }', mobile_header: '') c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '', header: '', mobile_stylesheet: '$black: #000; #a { color: $black; }', mobile_header: '')
c.mobile_stylesheet_baked.should == "#a {\n color: black; }\n" c.mobile_stylesheet_baked.should == "#a{color:#000}\n"
end
it 'should allow including discourse styles' do
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '@import "desktop";', mobile_stylesheet: '@import "mobile";')
c.stylesheet_baked.should_not =~ /Syntax error/
c.stylesheet_baked.length.should > 1000
c.mobile_stylesheet_baked.should_not =~ /Syntax error/
c.mobile_stylesheet_baked.length.should > 1000
end end
it 'should provide an awesome error on failure' do it 'should provide an awesome error on failure' do