Add ability to customize css and header for mobile

This commit is contained in:
Neil Lalonde 2013-09-16 12:21:49 -04:00
parent c9ebf23561
commit 13f17b2a5c
10 changed files with 189 additions and 72 deletions

View file

@ -7,7 +7,7 @@
@module Discourse
**/
Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'override_default_style'],
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'mobile_stylesheet', 'mobile_header', 'override_default_style'],
init: function() {
this._super();
@ -33,7 +33,7 @@ Discourse.SiteCustomization = Discourse.Model.extend({
return changed;
}.property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'originals'),
}.property('override_default_style', 'enabled', 'name', 'stylesheet', 'header', 'mobile_stylesheet', 'mobile_header', 'originals'),
startTrackingChanges: function() {
var _this = this;
@ -62,6 +62,8 @@ Discourse.SiteCustomization = Discourse.Model.extend({
enabled: this.enabled,
stylesheet: this.stylesheet,
header: this.header,
mobile_stylesheet: this.mobile_stylesheet,
mobile_header: this.mobile_header,
override_default_style: this.override_default_style
};

View file

@ -12,25 +12,38 @@
{{#if selectedItem}}
<div class='current-style'>
<div class='admin-controls'>
<ul class="nav nav-pills">
<li {{bindAttr class="view.stylesheetActive:active"}}>
<a {{action selectStylesheet href="true" target="view"}}>{{i18n admin.customize.css}}</a>
</li>
<li {{bindAttr class="view.headerActive:active"}}>
<a {{action selectHeader href="true" target="view"}}>{{i18n admin.customize.header}}</a>
</li>
</ul>
</div>
{{#with selectedItem}}
{{textField class="style-name" value=name}}
<div class='admin-controls'>
<ul class="nav nav-pills">
<li {{bindAttr class="view.stylesheetActive:active"}}>
<a {{action selectStylesheet href="true" target="view"}}>{{i18n admin.customize.css}}</a>
</li>
<li {{bindAttr class="view.headerActive:active"}}>
<a {{action selectHeader href="true" target="view"}}>{{i18n admin.customize.header}}</a>
</li>
<li {{bindAttr class="view.mobileStylesheetActive:active"}}>
<a {{action selectMobileStylesheet href="true" target="view"}}>{{i18n admin.customize.mobile_css}}</a>
</li>
<li {{bindAttr class="view.mobileHeaderActive:active"}}>
<a {{action selectMobileHeader href="true" target="view"}}>{{i18n admin.customize.mobile_header}}</a>
</li>
</ul>
</div>
{{#if view.headerActive}}
{{aceEditor content=header mode="html"}}
{{/if}}
{{#if view.stylesheetActive}}
{{aceEditor content=stylesheet mode="scss"}}
{{/if}}
{{#if view.mobileHeaderActive}}
{{aceEditor content=mobile_header mode="html"}}
{{/if}}
{{#if view.mobileStylesheetActive}}
{{aceEditor content=mobile_stylesheet mode="scss"}}
{{/if}}
{{/with}}
<br>
<div class='status-actions'>

View file

@ -11,20 +11,16 @@
Discourse.AdminCustomizeView = Discourse.View.extend({
templateName: 'admin/templates/customize',
classNames: ['customize'],
headerActive: Ember.computed.equal('selected', 'header'),
stylesheetActive: Ember.computed.equal('selected', 'stylesheet'),
mobileHeaderActive: Ember.computed.equal('selected', 'mobileHeader'),
mobileStylesheetActive: Ember.computed.equal('selected', 'mobileStylesheet'),
init: function() {
this._super();
this.set('selected', 'stylesheet');
},
headerActive: (function() {
return this.get('selected') === 'header';
}).property('selected'),
stylesheetActive: (function() {
return this.get('selected') === 'stylesheet';
}).property('selected'),
selectHeader: function() {
this.set('selected', 'header');
},
@ -33,6 +29,14 @@ Discourse.AdminCustomizeView = Discourse.View.extend({
this.set('selected', 'stylesheet');
},
selectMobileHeader: function() {
this.set('selected', 'mobileHeader');
},
selectMobileStylesheet: function() {
this.set('selected', 'mobileStylesheet');
},
didInsertElement: function() {
var controller = this.get('controller');
return Mousetrap.bindGlobal(['meta+s', 'ctrl+s'], function() {

View file

@ -49,7 +49,7 @@ class Admin::SiteCustomizationsController < Admin::AdminController
private
def site_customization_params
params.require(:site_customization).permit(:name, :stylesheet, :header, :position, :enabled, :key, :override_default_style, :stylesheet_baked)
params.require(:site_customization).permit(:name, :stylesheet, :header, :mobile_stylesheet, :mobile_header, :position, :enabled, :key, :override_default_style, :stylesheet_baked)
end
def log_site_customization_change(old_record, new_params)

View file

@ -12,31 +12,35 @@ class SiteCustomization < ActiveRecord::Base
end
before_save do
if stylesheet_changed?
begin
self.stylesheet_baked = Sass.compile stylesheet
rescue Sass::SyntaxError => e
error = e.sass_backtrace_str("custom stylesheet")
error.gsub!("\n", '\A ')
error.gsub!("'", '\27 ')
['stylesheet', 'mobile_stylesheet'].each do |stylesheet_attr|
if self.send("#{stylesheet_attr}_changed?")
begin
self.send("#{stylesheet_attr}_baked=", Sass.compile(self.send(stylesheet_attr)))
rescue Sass::SyntaxError => e
error = e.sass_backtrace_str("custom stylesheet")
error.gsub!("\n", '\A ')
error.gsub!("'", '\27 ')
self.stylesheet_baked =
"#main {display: none;}
footer {white-space: pre; margin-left: 100px;}
footer:after{ content: '#{error}' }"
self.send("#{stylesheet_attr}_baked=",
"#main {display: none;}
footer {white-space: pre; margin-left: 100px;}
footer:after{ content: '#{error}' }")
end
end
end
end
after_save do
if stylesheet_changed?
if File.exists?(stylesheet_fullpath)
File.delete stylesheet_fullpath
end
File.delete(stylesheet_fullpath) if File.exists?(stylesheet_fullpath)
end
if mobile_stylesheet_changed?
File.delete(stylesheet_fullpath(:mobile)) if File.exists?(stylesheet_fullpath(:mobile))
end
remove_from_cache!
if stylesheet_changed?
ensure_stylesheet_on_disk!
if stylesheet_changed? or mobile_stylesheet_changed?
ensure_stylesheets_on_disk!
# TODO: this is broken now because there's mobile stuff too
MessageBus.publish "/file-change/#{key}", stylesheet_hash
end
MessageBus.publish "/header-change/#{key}", header if header_changed?
@ -47,6 +51,9 @@ footer:after{ content: '#{error}' }"
if File.exists?(stylesheet_fullpath)
File.delete stylesheet_fullpath
end
if File.exists?(stylesheet_fullpath(:mobile))
File.delete stylesheet_fullpath(:mobile)
end
self.remove_from_cache!
end
@ -71,17 +78,17 @@ footer:after{ content: '#{error}' }"
end
end
def self.custom_stylesheet(preview_style)
def self.custom_stylesheet(preview_style, target=:desktop)
preview_style ||= enabled_style_key
style = lookup_style(preview_style)
style.stylesheet_link_tag.html_safe if style
style.stylesheet_link_tag(target).html_safe if style
end
def self.custom_header(preview_style)
def self.custom_header(preview_style, target=:desktop)
preview_style ||= enabled_style_key
style = lookup_style(preview_style)
if style && style.header
style.header.html_safe
if style && ((target == :mobile && style.mobile_header) || style.header)
target == :mobile ? style.mobile_header.html_safe : style.header.html_safe
else
""
end
@ -104,7 +111,7 @@ footer:after{ content: '#{error}' }"
@lock.synchronize do
style = where(key: key).first
style.ensure_stylesheet_on_disk! if style
style.ensure_stylesheets_on_disk! if style
@cache[key] = style
end
end
@ -135,39 +142,49 @@ footer:after{ content: '#{error}' }"
self.class.remove_from_cache!(key)
end
def stylesheet_hash
Digest::MD5.hexdigest(stylesheet)
def stylesheet_hash(target=:desktop)
Digest::MD5.hexdigest( target == :mobile ? mobile_stylesheet : stylesheet )
end
def cache_fullpath
"#{Rails.root}/public/#{CACHE_PATH}"
end
def ensure_stylesheet_on_disk!
path = stylesheet_fullpath
dir = cache_fullpath
FileUtils.mkdir_p(dir)
unless File.exists?(path)
File.open(path, "w") do |f|
f.puts stylesheet_baked
def ensure_stylesheets_on_disk!
[[:desktop, 'stylesheet_baked'], [:mobile, 'mobile_stylesheet_baked']].each do |target, baked_attr|
path = stylesheet_fullpath(target)
dir = cache_fullpath
FileUtils.mkdir_p(dir)
unless File.exists?(path)
File.open(path, "w") do |f|
f.puts self.send(baked_attr)
end
end
end
end
def stylesheet_filename
"/#{self.key}.css"
def stylesheet_filename(target=:desktop)
target == :desktop ? "/#{self.key}.css" : "/#{target}_#{self.key}.css"
end
def stylesheet_fullpath
"#{cache_fullpath}#{stylesheet_filename}"
def stylesheet_fullpath(target=:desktop)
"#{cache_fullpath}#{stylesheet_filename(target)}"
end
def stylesheet_link_tag
def stylesheet_link_tag(target=:desktop)
return mobile_stylesheet_link_tag if target == :mobile
return "" unless stylesheet.present?
return @stylesheet_link_tag if @stylesheet_link_tag
ensure_stylesheet_on_disk!
ensure_stylesheets_on_disk!
@stylesheet_link_tag = "<link class=\"custom-css\" rel=\"stylesheet\" href=\"/#{CACHE_PATH}#{stylesheet_filename}?#{stylesheet_hash}\" type=\"text/css\" media=\"screen\">"
end
def mobile_stylesheet_link_tag
return "" unless mobile_stylesheet.present?
return @mobile_stylesheet_link_tag if @mobile_stylesheet_link_tag
ensure_stylesheets_on_disk!
@mobile_stylesheet_link_tag = "<link class=\"custom-css\" rel=\"stylesheet\" href=\"/#{CACHE_PATH}#{stylesheet_filename(:mobile)}?#{stylesheet_hash(:mobile)}\" type=\"text/css\" media=\"screen\">"
end
end
# == Schema Information

View file

@ -10,6 +10,4 @@
<%= stylesheet_link_tag "admin"%>
<%-end%>
<% unless mobile_view? %>
<%= SiteCustomization.custom_stylesheet(session[:preview_style]) %>
<% end %>
<%= SiteCustomization.custom_stylesheet(session[:preview_style], mobile_view? ? :mobile : :desktop) %>

View file

@ -30,9 +30,7 @@
<body>
<!--[if IE 9]><script type="text/javascript">ie = "new";</script><![endif]-->
<% unless mobile_view? %>
<%= SiteCustomization.custom_header(session[:preview_style]) %>
<% end %>
<%= SiteCustomization.custom_header(session[:preview_style], mobile_view? ? :mobile : :desktop) %>
<section id='main'>
</section>

View file

@ -1166,6 +1166,8 @@ en:
long_title: "Site Customizations"
header: "Header"
css: "Stylesheet"
mobile_header: "Mobile Header"
mobile_css: "Mobile Stylesheet"
override_default: "Do not include standard style sheet"
enabled: "Enabled?"
preview: "preview"

View file

@ -0,0 +1,7 @@
class AddMobileToSiteCustomizations < ActiveRecord::Migration
def change
add_column :site_customizations, :mobile_stylesheet, :text
add_column :site_customizations, :mobile_header, :text
add_column :site_customizations, :mobile_stylesheet_baked, :text
end
end

View file

@ -6,8 +6,16 @@ describe SiteCustomization do
Fabricate(:user)
end
let :customization_params do
{name: 'my name', user_id: user.id, header: "my awesome header", stylesheet: "my awesome css", mobile_stylesheet: '', mobile_header: ''}
end
let :customization do
SiteCustomization.create!(name: 'my name', user_id: user.id, header: "my awesome header", stylesheet: "my awesome css")
SiteCustomization.create!(customization_params)
end
let :customization_with_mobile do
SiteCustomization.create!(customization_params.merge(mobile_stylesheet: ".mobile {better: true;}", mobile_header: "fancy mobile stuff"))
end
it 'should set default key when creating a new customization' do
@ -50,15 +58,46 @@ describe SiteCustomization do
c = customization
c.remove_from_cache!
File.delete(c.stylesheet_fullpath)
File.delete(c.stylesheet_fullpath(:mobile))
SiteCustomization.custom_stylesheet(c.key)
File.exists?(c.stylesheet_fullpath).should == true
File.exists?(c.stylesheet_fullpath(:mobile)).should == true
end
it 'should allow me to lookup a filename containing my preview stylesheet' do
SiteCustomization.custom_stylesheet(customization.key).should ==
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/uploads/stylesheet-cache/#{customization.key}.css?#{customization.stylesheet_hash}\" type=\"text/css\" media=\"screen\">"
context '#custom_stylesheet' do
it 'should allow me to lookup a filename containing my preview stylesheet' do
SiteCustomization.custom_stylesheet(customization.key).should ==
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/uploads/stylesheet-cache/#{customization.key}.css?#{customization.stylesheet_hash}\" type=\"text/css\" media=\"screen\">"
end
it "should return blank link tag for mobile if mobile_stylesheet is blank" do
SiteCustomization.custom_stylesheet(customization.key, :mobile).should == ""
end
it "should return link tag for mobile custom stylesheet" do
SiteCustomization.custom_stylesheet(customization_with_mobile.key, :mobile).should ==
"<link class=\"custom-css\" rel=\"stylesheet\" href=\"/uploads/stylesheet-cache/mobile_#{customization_with_mobile.key}.css?#{customization_with_mobile.stylesheet_hash(:mobile)}\" type=\"text/css\" media=\"screen\">"
end
end
context '#custom_header' do
it "returns empty string when there is no custom header" do
c = SiteCustomization.create!(customization_params.merge(header: ''))
SiteCustomization.custom_header(c.key).should == ''
end
it "can return the custom header html" do
SiteCustomization.custom_header(customization.key).should == customization_params[:header]
end
it "returns empty string for mobile header when there's no custom mobile header" do
SiteCustomization.custom_header(customization.key, :mobile).should == ''
end
it "can return the custom mobile header html" do
SiteCustomization.custom_header(customization_with_mobile.key, :mobile).should == customization_with_mobile.mobile_header
end
end
it 'should fix stylesheet files after changing the stylesheet' do
@ -72,13 +111,31 @@ describe SiteCustomization do
SiteCustomization.custom_stylesheet(customization.key).should_not == original
end
it 'should fix mobile stylesheet files after changing the mobile_stylesheet' do
old_file = customization_with_mobile.stylesheet_fullpath(:mobile)
original = SiteCustomization.custom_stylesheet(customization_with_mobile.key, :mobile)
File.exists?(old_file).should == true
customization_with_mobile.mobile_stylesheet = "div { clear:both; }"
customization_with_mobile.save
SiteCustomization.custom_stylesheet(customization_with_mobile.key).should_not == original
end
it 'should delete old stylesheet files after deleting' do
old_file = customization.stylesheet_fullpath
customization.ensure_stylesheet_on_disk!
customization.ensure_stylesheets_on_disk!
customization.destroy
File.exists?(old_file).should == false
end
it 'should delete old mobile stylesheet files after deleting' do
old_file = customization_with_mobile.stylesheet_fullpath(:mobile)
customization_with_mobile.ensure_stylesheets_on_disk!
customization_with_mobile.destroy
File.exists?(old_file).should == false
end
it 'should nuke old revs out of the cache' do
old_style = SiteCustomization.custom_stylesheet(customization.key)
@ -87,16 +144,35 @@ describe SiteCustomization do
SiteCustomization.custom_stylesheet(customization.key).should_not == old_style
end
it 'should nuke old revs out of the cache for mobile too' do
old_style = SiteCustomization.custom_stylesheet(customization_with_mobile.key)
customization_with_mobile.mobile_stylesheet = "hello worldz"
customization_with_mobile.save
SiteCustomization.custom_stylesheet(customization.key, :mobile).should_not == old_style
end
it 'should compile scss' do
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"
end
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.mobile_stylesheet_baked.should == "#a {\n color: black; }\n"
end
it 'should provide an awesome error on failure' do
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: "$black: #000; #a { color: $black; }\n\n\nboom", header: '')
c.stylesheet_baked.should =~ /Syntax error/
c.mobile_stylesheet_baked.should_not be_present
end
it 'should provide an awesome error on failure for mobile too' do
c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '', header: '', mobile_stylesheet: "$black: #000; #a { color: $black; }\n\n\nboom", mobile_header: '')
c.mobile_stylesheet_baked.should =~ /Syntax error/
c.stylesheet_baked.should_not be_present
end
end