Merge branch 'rs-remove-user-color-scheme-class' into 'master'

Apply syntax highlighting to fenced code blocks client-side

Instead of applying the syntax highlighting scheme class to these blocks
server-side, we use Javascript and Gon to apply the user's color scheme
(or the default) client-side.

This will make it easier to cache these blocks in the future because
they're no longer state-dependent.

See merge request !1203
This commit is contained in:
Douwe Maan 2015-08-27 16:24:51 +00:00
commit 30555c1d24
23 changed files with 245 additions and 102 deletions

View file

@ -0,0 +1,9 @@
# Applies a syntax highlighting color scheme CSS class to any element with the
# `js-syntax-highlight` class
#
# ### Example Markup
#
# <div class="js-syntax-highlight"></div>
#
$(document).on 'ready page:load', ->
$('.js-syntax-highlight').addClass(gon.user_color_scheme)

View file

@ -132,10 +132,6 @@ p.time {
text-shadow: none;
}
.highlight_word {
background: #fafe3d;
}
.thin_area{
height: 150px;
}

View file

@ -21,6 +21,12 @@ pre.code.highlight.dark,
background-color: #557 !important;
}
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
}
.hll { background-color: #373b41 }
.c { color: #969896 } /* Comment */
.err { color: #cc6666 } /* Error */

View file

@ -21,6 +21,12 @@ pre.code.monokai,
background-color: #49483e !important;
}
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
}
.hll { background-color: #49483e }
.c { color: #75715e } /* Comment */
.err { color: #960050; background-color: #1e0010 } /* Error */

View file

@ -21,6 +21,11 @@ pre.code.highlight.solarized-dark,
background-color: #174652 !important;
}
// Search result highlight
span.highlight_word {
background: #094554;
}
/* Solarized Dark
For use with Jekyll and Pygments

View file

@ -21,6 +21,11 @@ pre.code.highlight.solarized-light,
background-color: #ddd8c5 !important;
}
// Search result highlight
span.highlight_word {
background: #eee8d5;
}
/* Solarized Light
For use with Jekyll and Pygments

View file

@ -21,6 +21,11 @@ pre.code.highlight.white,
background-color: #f8eec7 !important;
}
// Search result highlight
span.highlight_word {
background: #fafe3d;
}
.hll { background-color: #f8f8f8 }
.c { color: #999988; font-style: italic; }
.err { color: #a61717; background-color: #e3d2d2; }

View file

@ -192,11 +192,12 @@ class ApplicationController < ActionController::Base
end
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size;
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if current_user
gon.current_user_id = current_user.id

View file

@ -58,7 +58,7 @@ module GitlabMarkdownHelper
@options = options
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch
rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options)
rend = Redcarpet::Render::GitlabHTML.new(self, options)
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS)

View file

@ -1,25 +1,5 @@
# Helper methods for per-User preferences
module PreferencesHelper
COLOR_SCHEMES = {
1 => 'white',
2 => 'dark',
3 => 'solarized-light',
4 => 'solarized-dark',
5 => 'monokai',
}
COLOR_SCHEMES.default = 'white'
# Helper method to access the COLOR_SCHEMES
#
# The keys are the `color_scheme_ids`
# The values are the `name` of the scheme.
#
# The preview images are `name-scheme-preview.png`
# The stylesheets should use the css class `.name`
def color_schemes
COLOR_SCHEMES.freeze
end
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
projects: 'Your Projects (default)',
@ -50,12 +30,11 @@ module PreferencesHelper
end
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
Gitlab::Themes.for_user(current_user).css_class
end
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class
end
def prefer_readme?

View file

@ -22,11 +22,11 @@
.panel-heading
Syntax highlighting theme
.panel-body
- color_schemes.each do |color_scheme_id, color_scheme|
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
.preview= image_tag "#{color_scheme}-scheme-preview.png"
= f.radio_button :color_scheme_id, color_scheme_id
= color_scheme.tr('-_', ' ').titleize
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
.panel.panel-default
.panel-heading

View file

@ -7,4 +7,4 @@
%strong
= blob.filename
.file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white'
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline

View file

@ -23,7 +23,7 @@
.nothing-here-block Empty file
- else
.file-content.code
%div.highlighted-data{class: user_color_scheme_class}
%div.highlighted-data{ class: user_color_scheme }
.line-numbers
- snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty?

View file

@ -7,4 +7,4 @@
%strong
= wiki_blob.filename
.file-content.code.term
= render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white'
= render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline

View file

@ -1,4 +1,4 @@
.file-content.code{class: user_color_scheme_class}
.file-content.code.js-syntax-highlight{ class: user_color_scheme }
.line-numbers
- if blob.data.present?
- blob.data.lines.each_index do |index|

View file

@ -0,0 +1,67 @@
module Gitlab
# Module containing GitLab's syntax color scheme definitions and helper
# methods for accessing them.
module ColorSchemes
# Struct class representing a single Scheme
Scheme = Struct.new(:id, :name, :css_class)
SCHEMES = [
Scheme.new(1, 'White', 'white'),
Scheme.new(2, 'Dark', 'dark'),
Scheme.new(3, 'Solarized Light', 'solarized-light'),
Scheme.new(4, 'Solarized Dark', 'solarized-dark'),
Scheme.new(5, 'Monokai', 'monokai')
].freeze
# Convenience method to get a space-separated String of all the color scheme
# classes that might be applied to a code block.
#
# Returns a String
def self.body_classes
SCHEMES.collect(&:css_class).uniq.join(' ')
end
# Get a Scheme by its ID
#
# If the ID is invalid, returns the default Scheme.
#
# id - Integer ID
#
# Returns a Scheme
def self.by_id(id)
SCHEMES.detect { |s| s.id == id } || default
end
# Returns the number of defined Schemes
def self.count
SCHEMES.size
end
# Get the default Scheme
#
# Returns a Scheme
def self.default
by_id(1)
end
# Iterate through each Scheme
#
# Yields the Scheme object
def self.each(&block)
SCHEMES.each(&block)
end
# Get the Scheme for the specified user, or the default
#
# user - User record
#
# Returns a Scheme
def self.for_user(user)
if user
by_id(user.color_scheme_id)
else
default
end
end
end
end

View file

@ -37,6 +37,11 @@ module Gitlab
THEMES.detect { |t| t.id == id } || default
end
# Returns the number of defined Themes
def self.count
THEMES.size
end
# Get the default Theme
#
# Returns a Theme
@ -51,6 +56,19 @@ module Gitlab
THEMES.each(&block)
end
# Get the Theme for the specified user, or the default
#
# user - User record
#
# Returns a Theme
def self.for_user(user)
if user
by_id(user.theme_id)
else
default
end
end
private
def self.default_id

View file

@ -4,9 +4,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
attr_reader :template
alias_method :h, :template
def initialize(template, color_scheme, options = {})
def initialize(template, options = {})
@template = template
@color_scheme = color_scheme
@options = options.dup
@options.reverse_merge!(
@ -35,7 +34,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
formatter = Rouge::Formatters::HTMLGitlab.new(
cssclass: "code highlight #{@color_scheme} #{lexer.tag}"
cssclass: "code highlight js-syntax-highlight #{lexer.tag}"
)
formatter.format(lexer.lex(code))
end

View file

@ -64,8 +64,8 @@ describe 'GitLab Markdown', feature: true do
it 'parses fenced code blocks' do
aggregate_failures do
expect(doc).to have_selector('pre.code.highlight.white.c')
expect(doc).to have_selector('pre.code.highlight.white.python')
expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c')
expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python')
end
end
@ -224,8 +224,4 @@ describe 'GitLab Markdown', feature: true do
def current_user
@feat.user
end
def user_color_scheme_class
:white
end
end

View file

@ -28,8 +28,7 @@ describe EventsHelper do
it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
expected = '<pre class="code highlight white plaintext"><code>' \
'Code block...</code></pre>'
expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
expect(event_note(input)).to match(expected)
end
@ -55,7 +54,7 @@ describe EventsHelper do
it 'should preserve code color scheme' do
input = "```ruby\ndef test\n 'hello world'\nend\n```"
expected = '<pre class="code highlight white ruby">' \
expected = '<pre class="code highlight js-syntax-highlight ruby">' \
"<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
" <span class=\"s1\">\'hello world\'</span>\n" \
"<span class=\"k\">end</span>" \

View file

@ -1,72 +1,82 @@
require 'spec_helper'
describe PreferencesHelper do
describe 'user_application_theme' do
context 'with a user' do
it "returns user's theme's css_class" do
user = double('user', theme_id: 3)
allow(self).to receive(:current_user).and_return(user)
expect(user_application_theme).to eq 'ui_green'
end
it 'returns the default when id is invalid' do
user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
allow(self).to receive(:current_user).and_return(user)
expect(user_application_theme).to eq 'ui_charcoal'
end
end
context 'without a user' do
before do
allow(self).to receive(:current_user).and_return(nil)
end
it 'returns the default theme' do
expect(user_application_theme).to eq Gitlab::Themes.default.css_class
end
end
end
describe 'dashboard_choices' do
it 'raises an exception when defined choices may be missing' do
expect(User).to receive(:dashboards).and_return(foo: 'foo')
expect { dashboard_choices }.to raise_error(RuntimeError)
expect { helper.dashboard_choices }.to raise_error(RuntimeError)
end
it 'raises an exception when defined choices may be using the wrong key' do
expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
expect { dashboard_choices }.to raise_error(KeyError)
expect { helper.dashboard_choices }.to raise_error(KeyError)
end
it 'provides better option descriptions' do
expect(dashboard_choices).to match_array [
expect(helper.dashboard_choices).to match_array [
['Your Projects (default)', 'projects'],
['Starred Projects', 'stars']
]
end
end
describe 'user_color_scheme_class' do
context 'with current_user is nil' do
it 'should return a string' do
allow(self).to receive(:current_user).and_return(nil)
expect(user_color_scheme_class).to be_kind_of(String)
describe 'user_application_theme' do
context 'with a user' do
it "returns user's theme's css_class" do
stub_user(theme_id: 3)
expect(helper.user_application_theme).to eq 'ui_green'
end
it 'returns the default when id is invalid' do
stub_user(theme_id: Gitlab::Themes.count + 5)
allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
expect(helper.user_application_theme).to eq 'ui_charcoal'
end
end
context 'with a current_user' do
(1..5).each do |color_scheme_id|
context "with color_scheme_id == #{color_scheme_id}" do
it 'should return a string' do
current_user = double(color_scheme_id: color_scheme_id)
allow(self).to receive(:current_user).and_return(current_user)
expect(user_color_scheme_class).to be_kind_of(String)
end
end
context 'without a user' do
it 'returns the default theme' do
stub_user
expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
end
end
end
describe 'user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do
allow(helper).to receive(:current_user).
and_return(double(color_scheme_id: 3))
expect(helper.user_color_scheme).to eq 'solarized-light'
end
it 'returns the default when id is invalid' do
allow(helper).to receive(:current_user).
and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
end
end
context 'without a user' do
it 'returns the default theme' do
stub_user
expect(helper.user_color_scheme).
to eq Gitlab::ColorSchemes.default.css_class
end
end
end
def stub_user(messages = {})
if messages.empty?
allow(helper).to receive(:current_user).and_return(nil)
else
allow(helper).to receive(:current_user).
and_return(double('user', messages))
end
end
end

View file

@ -0,0 +1,45 @@
require 'spec_helper'
describe Gitlab::ColorSchemes do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
expect(css).to include('white')
expect(css).to include(' solarized-light ')
expect(css).to include(' monokai')
end
end
describe '.by_id' do
it 'returns a scheme by its ID' do
expect(described_class.by_id(1).name).to eq 'White'
expect(described_class.by_id(4).name).to eq 'Solarized Dark'
end
end
describe '.default' do
it 'returns the default scheme' do
expect(described_class.default.id).to eq 1
end
end
describe '.each' do
it 'passes the block to the SCHEMES Array' do
ids = []
described_class.each { |scheme| ids << scheme.id }
expect(ids).not_to be_empty
end
end
describe '.for_user' do
it 'returns default when user is nil' do
expect(described_class.for_user(nil).id).to eq 1
end
it "returns user's preferred color scheme" do
user = double(color_scheme_id: 5)
expect(described_class.for_user(user).id).to eq 5
end
end
end

View file

@ -43,9 +43,6 @@ describe Gitlab::Themes do
ids = []
described_class.each { |theme| ids << theme.id }
expect(ids).not_to be_empty
# TODO (rspeicher): RSpec 3.x
# expect(described_class.each).to yield_with_arg(described_class::Theme)
end
end
end