diff --git a/Gemfile b/Gemfile index 335a4375..298c2d3a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'http://rubygems.org' ruby '2.1.1' -gem 'rails', '~> 4.1.0.rc2' +gem 'rails', '~> 4.1.0' gem 'json' gem 'thin' diff --git a/Gemfile.lock b/Gemfile.lock index 0c7c41ee..dc9879dc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,34 +10,33 @@ GIT GEM remote: http://rubygems.org/ specs: - actionmailer (4.1.0.rc2) - actionpack (= 4.1.0.rc2) - actionview (= 4.1.0.rc2) + actionmailer (4.1.0) + actionpack (= 4.1.0) + actionview (= 4.1.0) mail (~> 2.5.4) - actionpack (4.1.0.rc2) - actionview (= 4.1.0.rc2) - activesupport (= 4.1.0.rc2) + actionpack (4.1.0) + actionview (= 4.1.0) + activesupport (= 4.1.0) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionview (4.1.0.rc2) - activesupport (= 4.1.0.rc2) + actionview (4.1.0) + activesupport (= 4.1.0) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.0.rc2) - activesupport (= 4.1.0.rc2) + activemodel (4.1.0) + activesupport (= 4.1.0) builder (~> 3.1) - activerecord (4.1.0.rc2) - activemodel (= 4.1.0.rc2) - activesupport (= 4.1.0.rc2) + activerecord (4.1.0) + activemodel (= 4.1.0) + activesupport (= 4.1.0) arel (~> 5.0.0) - activesupport (4.1.0.rc2) + activesupport (4.1.0) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.1) tzinfo (~> 1.1) arel (5.0.0) - atomic (1.1.16) bootstrap-sass (3.1.1.0) sass (~> 3.2) builder (3.2.2) @@ -62,19 +61,19 @@ GEM rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rails (4.1.0.rc2) - actionmailer (= 4.1.0.rc2) - actionpack (= 4.1.0.rc2) - actionview (= 4.1.0.rc2) - activemodel (= 4.1.0.rc2) - activerecord (= 4.1.0.rc2) - activesupport (= 4.1.0.rc2) + rails (4.1.0) + actionmailer (= 4.1.0) + actionpack (= 4.1.0) + actionview (= 4.1.0) + activemodel (= 4.1.0) + activerecord (= 4.1.0) + activesupport (= 4.1.0) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.0.rc2) - sprockets-rails (~> 2.0.0) - railties (4.1.0.rc2) - actionpack (= 4.1.0.rc2) - activesupport (= 4.1.0.rc2) + railties (= 4.1.0) + sprockets-rails (~> 2.0) + railties (4.1.0) + actionpack (= 4.1.0) + activesupport (= 4.1.0) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.2.2) @@ -99,8 +98,7 @@ GEM eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.19.1) - thread_safe (0.3.1) - atomic (>= 1.1.7, < 2) + thread_safe (0.3.3) tilt (1.4.1) treetop (1.4.15) polyglot @@ -119,7 +117,7 @@ DEPENDENCIES jquery-rails json pg - rails (~> 4.1.0.rc2) + rails (~> 4.1.0) sass-rails (~> 4.0.2) simple_form (~> 3.0.0)! sqlite3 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 51c445bc..161c2050 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -7,4 +7,6 @@ //= require jquery //= require jquery_ujs //= require bootstrap +//= require rainbow +//= require ruby // diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index ed6a599e..dbb0d29f 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,5 +1,6 @@ /* *= require bootstrap + *= require github */ body { diff --git a/app/controllers/documentation_controller.rb b/app/controllers/documentation_controller.rb new file mode 100644 index 00000000..c0a678de --- /dev/null +++ b/app/controllers/documentation_controller.rb @@ -0,0 +1,5 @@ +class DocumentationController < ApplicationController + def index + @user_basic = User.new + end +end diff --git a/app/views/documentation/form_example.html.erb b/app/views/documentation/form_example.html.erb new file mode 100644 index 00000000..2790789e --- /dev/null +++ b/app/views/documentation/form_example.html.erb @@ -0,0 +1,22 @@ + +<%%= simple_form_for @user_basic, url: create_basic_examples_url, as: 'user_basic' do |f| %> + + <%%= f.input :email, placeholder: 'Enter email' %> + + <%%= f.input :password, placeholder: 'Password' %> + + <%%= f.input :file, as: :file, wrapper: :vertical_file_input %> + + <%%= f.input :active, wrapper: :vertical_boolean %> + + <%%= f.input :choices, as: :check_boxes, + collection: [ + 'Option one is this and that—be sure to include why it\'s great', + 'Option two can be something else and selecting it will deselect option one'], + wrapper: :vertical_radio_and_checkboxes %> + + <%%= f.input :sex, as: :radio_buttons, + collection: ['Male', 'Female'], wrapper: :vertical_radio_and_checkboxes %> + + <%%= f.button :submit %> +<%% end %> diff --git a/app/views/documentation/form_example_with_wrapper_mappings.html.erb b/app/views/documentation/form_example_with_wrapper_mappings.html.erb new file mode 100644 index 00000000..3a17d5dc --- /dev/null +++ b/app/views/documentation/form_example_with_wrapper_mappings.html.erb @@ -0,0 +1,27 @@ + +<%%= simple_form_for @user_basic, url: create_basic_examples_url, as: 'user_basic', + wrapper_mappings: { + check_boxes: :vertical_radio_and_checkboxes, + radio_buttons: :vertical_radio_and_checkboxes, + file: :vertical_file_input, + boolean: :vertical_boolean + } do |f| %> + + <%%= f.input :email, placeholder: 'Enter email' %> + + <%%= f.input :password, placeholder: 'Password' %> + + <%%= f.input :file, as: :file %> + + <%%= f.input :active %> + + <%%= f.input :choices, as: :check_boxes, + collection: [ + 'Option one is this and that—be sure to include why it\'s great', + 'Option two can be something else and selecting it will deselect option one'] %> + + <%%= f.input :sex, as: :radio_buttons, + collection: ['Male', 'Female'] %> + + <%%= f.button :submit %> +<%% end %> diff --git a/app/views/documentation/index.html.erb b/app/views/documentation/index.html.erb new file mode 100644 index 00000000..98db7bcf --- /dev/null +++ b/app/views/documentation/index.html.erb @@ -0,0 +1,77 @@ +<% content_for :breadcrumb do %> +
  • <%= link_to "Home", root_path %>
  • +
  • Documentation
  • +<% end %> + +
    +

    How to build your Rails application with Simple Form and Bootstrap

    +
    + +
    +

    Configuring your Rails app with Simple Form and Bootstrap

    +
      +
    1. Create your new Rails application: rails new my_new_app
    2. +
    3. Open the generated Gemfile and add the Simple Form dependency: gem 'simple_form'
    4. +
    5. Install the dependencies: bundle install
    6. +
    7. Install the Simple Form, using the --bootstrap option: rails generate simple_form:install --bootstrap
    8. +
    9. Now you have a new Rails application using Simple Form and configured to use Bootstrap
    10. +
    +
    + +
    +

    Building your first form

    + +

    Example: Lets build the following form.

    +
    + +
    + <%= render partial: 'examples/basic_example_sf', layout: 'examples/sf_label', locals: { title: 'Basic Form' } %> +
    + +
    +

    The code is straightforward and looks like this:

    + +
    +    
    +      <%= render file: 'documentation/form_example' %>
    +    
    +  
    +
    + +
    +

    The code is very simple, isn't it?

    + +

    + But it can get better! We can use the wrapper_mapping option + to remove that wrapper duplication. This option receives a Hash containing an input + type and the wrapper that will be used for all inputs with specified type. +

    + +

    + Example: +

    +      { string: :string_wrapper, boolean: :boolean_wrapper }
    +    
    + + All the String inputs will now use the :string_wrapper, and the + same applies to boolean fields, which will use the :boolean_wrapper + for all its inputs. + You can see more information about wrapper_mappings + <%= link_to 'here.', 'https://github.com/plataformatec/simple_form/blob/v3.0.2/lib/simple_form.rb#L107-L111' %> +

    +
    + +
    +
    +    
    +      <%= render file: 'documentation/form_example_with_wrapper_mappings' %>
    +    
    +  
    +
    + +
    +

    + Simple, right? You can see the code for the other examples + <%= link_to 'on GitHub.', 'https://github.com/rafaelfranca/simple_form-bootstrap' %> +

    +
    diff --git a/app/views/examples/_basic_example_sf.html.erb b/app/views/examples/_basic_example_sf.html.erb index 02b0ccba..7a146cde 100644 --- a/app/views/examples/_basic_example_sf.html.erb +++ b/app/views/examples/_basic_example_sf.html.erb @@ -5,6 +5,7 @@ file: :vertical_file_input, boolean: :vertical_boolean } do |f| %> + <%= f.input :email, placeholder: 'Enter email' %> <%= f.input :password, placeholder: 'Password' %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index e7fe42ea..99152b4e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,6 +19,7 @@ diff --git a/config/routes.rb b/config/routes.rb index 437b4b15..c38ee967 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + get 'documentation', to: 'documentation#index', as: :documentation + resources :examples, only: :index do collection do post :create_basic diff --git a/vendor/assets/javascripts/rainbow.js b/vendor/assets/javascripts/rainbow.js new file mode 100644 index 00000000..81904534 --- /dev/null +++ b/vendor/assets/javascripts/rainbow.js @@ -0,0 +1,798 @@ +/** + * Copyright 2013 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Rainbow is a simple code syntax highlighter + * + * @preserve @version 1.2 + * @url rainbowco.de + */ +window['Rainbow'] = (function() { + + /** + * array of replacements to process at the end + * + * @type {Object} + */ + var replacements = {}, + + /** + * an array of start and end positions of blocks to be replaced + * + * @type {Object} + */ + replacement_positions = {}, + + /** + * an array of the language patterns specified for each language + * + * @type {Object} + */ + language_patterns = {}, + + /** + * an array of languages and whether they should bypass the default patterns + * + * @type {Object} + */ + bypass_defaults = {}, + + /** + * processing level + * + * replacements are stored at this level so if there is a sub block of code + * (for example php inside of html) it runs at a different level + * + * @type {number} + */ + CURRENT_LEVEL = 0, + + /** + * constant used to refer to the default language + * + * @type {number} + */ + DEFAULT_LANGUAGE = 0, + + /** + * used as counters so we can selectively call setTimeout + * after processing a certain number of matches/replacements + * + * @type {number} + */ + match_counter = 0, + + /** + * @type {number} + */ + replacement_counter = 0, + + /** + * @type {null|string} + */ + global_class, + + /** + * @type {null|Function} + */ + onHighlight; + + /** + * cross browser get attribute for an element + * + * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method + * + * @param {Node} el + * @param {string} attr attribute you are trying to get + * @returns {string|number} + */ + function _attr(el, attr, attrs, i) { + var result = (el.getAttribute && el.getAttribute(attr)) || 0; + + if (!result) { + attrs = el.attributes; + + for (i = 0; i < attrs.length; ++i) { + if (attrs[i].nodeName === attr) { + return attrs[i].nodeValue; + } + } + } + + return result; + } + + /** + * adds a class to a given code block + * + * @param {Element} el + * @param {string} class_name class name to add + * @returns void + */ + function _addClass(el, class_name) { + el.className += el.className ? ' ' + class_name : class_name; + } + + /** + * checks if a block has a given class + * + * @param {Element} el + * @param {string} class_name class name to check for + * @returns {boolean} + */ + function _hasClass(el, class_name) { + return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1; + } + + /** + * gets the language for this block of code + * + * @param {Element} block + * @returns {string|null} + */ + function _getLanguageForBlock(block) { + + // if this doesn't have a language but the parent does then use that + // this means if for example you have:
    +        // with a bunch of  blocks inside then you do not have
    +        // to specify the language for each block
    +        var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');
    +
    +        // this adds support for specifying language via a css class
    +        // you can use the Google Code Prettify style: 
    +        // or the HTML5 style: 
    
    +        if (!language) {
    +            var pattern = /\blang(?:uage)?-(\w+)/,
    +                match = block.className.match(pattern) || block.parentNode.className.match(pattern);
    +
    +            if (match) {
    +                language = match[1];
    +            }
    +        }
    +
    +        return language;
    +    }
    +
    +    /**
    +     * makes sure html entities are always used for tags
    +     *
    +     * @param {string} code
    +     * @returns {string}
    +     */
    +    function _htmlEntities(code) {
    +        return code.replace(//g, '>').replace(/&(?![\w\#]+;)/g, '&');
    +    }
    +
    +    /**
    +     * determines if a new match intersects with an existing one
    +     *
    +     * @param {number} start1    start position of existing match
    +     * @param {number} end1      end position of existing match
    +     * @param {number} start2    start position of new match
    +     * @param {number} end2      end position of new match
    +     * @returns {boolean}
    +     */
    +    function _intersects(start1, end1, start2, end2) {
    +        if (start2 >= start1 && start2 < end1) {
    +            return true;
    +        }
    +
    +        return end2 > start1 && end2 < end1;
    +    }
    +
    +    /**
    +     * determines if two different matches have complete overlap with each other
    +     *
    +     * @param {number} start1   start position of existing match
    +     * @param {number} end1     end position of existing match
    +     * @param {number} start2   start position of new match
    +     * @param {number} end2     end position of new match
    +     * @returns {boolean}
    +     */
    +    function _hasCompleteOverlap(start1, end1, start2, end2) {
    +
    +        // if the starting and end positions are exactly the same
    +        // then the first one should stay and this one should be ignored
    +        if (start2 == start1 && end2 == end1) {
    +            return false;
    +        }
    +
    +        return start2 <= start1 && end2 >= end1;
    +    }
    +
    +    /**
    +     * determines if the match passed in falls inside of an existing match
    +     * this prevents a regex pattern from matching inside of a bigger pattern
    +     *
    +     * @param {number} start - start position of new match
    +     * @param {number} end - end position of new match
    +     * @returns {boolean}
    +     */
    +    function _matchIsInsideOtherMatch(start, end) {
    +        for (var key in replacement_positions[CURRENT_LEVEL]) {
    +            key = parseInt(key, 10);
    +
    +            // if this block completely overlaps with another block
    +            // then we should remove the other block and return false
    +            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
    +                delete replacement_positions[CURRENT_LEVEL][key];
    +                delete replacements[CURRENT_LEVEL][key];
    +            }
    +
    +            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * takes a string of code and wraps it in a span tag based on the name
    +     *
    +     * @param {string} name     name of the pattern (ie keyword.regex)
    +     * @param {string} code     block of code to wrap
    +     * @returns {string}
    +     */
    +    function _wrapCodeInSpan(name, code) {
    +        return '' + code + '';
    +    }
    +
    +    /**
    +     * finds out the position of group match for a regular expression
    +     *
    +     * @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match
    +     *
    +     * @param {Object} match
    +     * @param {number} group_number
    +     * @returns {number}
    +     */
    +    function _indexOfGroup(match, group_number) {
    +        var index = 0,
    +            i;
    +
    +        for (i = 1; i < group_number; ++i) {
    +            if (match[i]) {
    +                index += match[i].length;
    +            }
    +        }
    +
    +        return index;
    +    }
    +
    +    /**
    +     * matches a regex pattern against a block of code
    +     * finds all matches that should be processed and stores the positions
    +     * of where they should be replaced within the string
    +     *
    +     * this is where pretty much all the work is done but it should not
    +     * be called directly
    +     *
    +     * @param {RegExp} pattern
    +     * @param {string} code
    +     * @returns void
    +     */
    +    function _processPattern(regex, pattern, code, callback)
    +    {
    +        if (typeof regex === "undefined" || regex === null) {
    +            //console.warn("undefined regular expression")
    +            return callback();
    +        }
    +        var match = regex.exec(code);
    +
    +        if (!match) {
    +            return callback();
    +        }
    +
    +        ++match_counter;
    +
    +        // treat match 0 the same way as name
    +        if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {
    +            pattern['name'] = pattern['matches'][0];
    +            delete pattern['matches'][0];
    +        }
    +
    +        var replacement = match[0],
    +            start_pos = match.index,
    +            end_pos = match[0].length + start_pos,
    +
    +            /**
    +             * callback to process the next match of this pattern
    +             */
    +            processNext = function() {
    +                var nextCall = function() {
    +                    _processPattern(regex, pattern, code, callback);
    +                };
    +
    +                // every 100 items we process let's call set timeout
    +                // to let the ui breathe a little
    +                return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
    +            };
    +
    +        // if this is not a child match and it falls inside of another
    +        // match that already happened we should skip it and continue processing
    +        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
    +            return processNext();
    +        }
    +
    +        /**
    +         * callback for when a match was successfully processed
    +         *
    +         * @param {string} replacement
    +         * @returns void
    +         */
    +        var onMatchSuccess = function(replacement) {
    +                // if this match has a name then wrap it in a span tag
    +                if (pattern['name']) {
    +                    replacement = _wrapCodeInSpan(pattern['name'], replacement);
    +                }
    +
    +                // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);
    +
    +                // store what needs to be replaced with what at this position
    +                if (!replacements[CURRENT_LEVEL]) {
    +                    replacements[CURRENT_LEVEL] = {};
    +                    replacement_positions[CURRENT_LEVEL] = {};
    +                }
    +
    +                replacements[CURRENT_LEVEL][start_pos] = {
    +                    'replace': match[0],
    +                    'with': replacement
    +                };
    +
    +                // store the range of this match so we can use it for comparisons
    +                // with other matches later
    +                replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;
    +
    +                // process the next match
    +                processNext();
    +            },
    +
    +            // if this pattern has sub matches for different groups in the regex
    +            // then we should process them one at a time by rerunning them through
    +            // this function to generate the new replacement
    +            //
    +            // we run through them backwards because the match position of earlier
    +            // matches will not change depending on what gets replaced in later
    +            // matches
    +            group_keys = keys(pattern['matches']),
    +
    +            /**
    +             * callback for processing a sub group
    +             *
    +             * @param {number} i
    +             * @param {Array} group_keys
    +             * @param {Function} callback
    +             */
    +            processGroup = function(i, group_keys, callback) {
    +                if (i >= group_keys.length) {
    +                    return callback(replacement);
    +                }
    +
    +                var processNextGroup = function() {
    +                        processGroup(++i, group_keys, callback);
    +                    },
    +                    block = match[group_keys[i]];
    +
    +                // if there is no match here then move on
    +                if (!block) {
    +                    return processNextGroup();
    +                }
    +
    +                var group = pattern['matches'][group_keys[i]],
    +                    language = group['language'],
    +
    +                    /**
    +                     * process group is what group we should use to actually process
    +                     * this match group
    +                     *
    +                     * for example if the subgroup pattern looks like this
    +                     * 2: {
    +                     *     'name': 'keyword',
    +                     *     'pattern': /true/g
    +                     * }
    +                     *
    +                     * then we use that as is, but if it looks like this
    +                     *
    +                     * 2: {
    +                     *     'name': 'keyword',
    +                     *     'matches': {
    +                     *          'name': 'special',
    +                     *          'pattern': /whatever/g
    +                     *      }
    +                     * }
    +                     *
    +                     * we treat the 'matches' part as the pattern and keep
    +                     * the name around to wrap it with later
    +                     */
    +                    process_group = group['name'] && group['matches'] ? group['matches'] : group,
    +
    +                    /**
    +                     * takes the code block matched at this group, replaces it
    +                     * with the highlighted block, and optionally wraps it with
    +                     * a span with a name
    +                     *
    +                     * @param {string} block
    +                     * @param {string} replace_block
    +                     * @param {string|null} match_name
    +                     */
    +                    _replaceAndContinue = function(block, replace_block, match_name) {
    +                        replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);
    +                        processNextGroup();
    +                    };
    +
    +                // if this is a sublanguage go and process the block using that language
    +                if (language) {
    +                    return _highlightBlockForLanguage(block, language, function(code) {
    +                        _replaceAndContinue(block, code);
    +                    });
    +                }
    +
    +                // if this is a string then this match is directly mapped to selector
    +                // so all we have to do is wrap it in a span and continue
    +                if (typeof group === 'string') {
    +                    return _replaceAndContinue(block, block, group);
    +                }
    +
    +                // the process group can be a single pattern or an array of patterns
    +                // _processCodeWithPatterns always expects an array so we convert it here
    +                _processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {
    +                    _replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);
    +                });
    +            };
    +
    +        processGroup(0, group_keys, onMatchSuccess);
    +    }
    +
    +    /**
    +     * should a language bypass the default patterns?
    +     *
    +     * if you call Rainbow.extend() and pass true as the third argument
    +     * it will bypass the defaults
    +     */
    +    function _bypassDefaultPatterns(language)
    +    {
    +        return bypass_defaults[language];
    +    }
    +
    +    /**
    +     * returns a list of regex patterns for this language
    +     *
    +     * @param {string} language
    +     * @returns {Array}
    +     */
    +    function _getPatternsForLanguage(language) {
    +        var patterns = language_patterns[language] || [],
    +            default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];
    +
    +        return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);
    +    }
    +
    +    /**
    +     * substring replace call to replace part of a string at a certain position
    +     *
    +     * @param {number} position         the position where the replacement should happen
    +     * @param {string} replace          the text we want to replace
    +     * @param {string} replace_with     the text we want to replace it with
    +     * @param {string} code             the code we are doing the replacing in
    +     * @returns {string}
    +     */
    +    function _replaceAtPosition(position, replace, replace_with, code) {
    +        var sub_string = code.substr(position);
    +        return code.substr(0, position) + sub_string.replace(replace, replace_with);
    +    }
    +
    +   /**
    +     * sorts an object by index descending
    +     *
    +     * @param {Object} object
    +     * @return {Array}
    +     */
    +    function keys(object) {
    +        var locations = [],
    +            replacement,
    +            pos;
    +
    +        for(var location in object) {
    +            if (object.hasOwnProperty(location)) {
    +                locations.push(location);
    +            }
    +        }
    +
    +        // numeric descending
    +        return locations.sort(function(a, b) {
    +            return b - a;
    +        });
    +    }
    +
    +    /**
    +     * processes a block of code using specified patterns
    +     *
    +     * @param {string} code
    +     * @param {Array} patterns
    +     * @returns void
    +     */
    +    function _processCodeWithPatterns(code, patterns, callback)
    +    {
    +        // we have to increase the level here so that the
    +        // replacements will not conflict with each other when
    +        // processing sub blocks of code
    +        ++CURRENT_LEVEL;
    +
    +        // patterns are processed one at a time through this function
    +        function _workOnPatterns(patterns, i)
    +        {
    +            // still have patterns to process, keep going
    +            if (i < patterns.length) {
    +                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
    +                    _workOnPatterns(patterns, ++i);
    +                });
    +            }
    +
    +            // we are done processing the patterns
    +            // process the replacements and update the DOM
    +            _processReplacements(code, function(code) {
    +
    +                // when we are done processing replacements
    +                // we are done at this level so we can go back down
    +                delete replacements[CURRENT_LEVEL];
    +                delete replacement_positions[CURRENT_LEVEL];
    +                --CURRENT_LEVEL;
    +                callback(code);
    +            });
    +        }
    +
    +        _workOnPatterns(patterns, 0);
    +    }
    +
    +    /**
    +     * process replacements in the string of code to actually update the markup
    +     *
    +     * @param {string} code         the code to process replacements in
    +     * @param {Function} onComplete   what to do when we are done processing
    +     * @returns void
    +     */
    +    function _processReplacements(code, onComplete) {
    +
    +        /**
    +         * processes a single replacement
    +         *
    +         * @param {string} code
    +         * @param {Array} positions
    +         * @param {number} i
    +         * @param {Function} onComplete
    +         * @returns void
    +         */
    +        function _processReplacement(code, positions, i, onComplete) {
    +            if (i < positions.length) {
    +                ++replacement_counter;
    +                var pos = positions[i],
    +                    replacement = replacements[CURRENT_LEVEL][pos];
    +                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
    +
    +                // process next function
    +                var next = function() {
    +                    _processReplacement(code, positions, ++i, onComplete);
    +                };
    +
    +                // use a timeout every 250 to not freeze up the UI
    +                return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);
    +            }
    +
    +            onComplete(code);
    +        }
    +
    +        var string_positions = keys(replacements[CURRENT_LEVEL]);
    +        _processReplacement(code, string_positions, 0, onComplete);
    +    }
    +
    +    /**
    +     * takes a string of code and highlights it according to the language specified
    +     *
    +     * @param {string} code
    +     * @param {string} language
    +     * @param {Function} onComplete
    +     * @returns void
    +     */
    +    function _highlightBlockForLanguage(code, language, onComplete) {
    +        var patterns = _getPatternsForLanguage(language);
    +        _processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);
    +    }
    +
    +    /**
    +     * highlight an individual code block
    +     *
    +     * @param {Array} code_blocks
    +     * @param {number} i
    +     * @returns void
    +     */
    +    function _highlightCodeBlock(code_blocks, i, onComplete) {
    +        if (i < code_blocks.length) {
    +            var block = code_blocks[i],
    +                language = _getLanguageForBlock(block);
    +
    +            if (!_hasClass(block, 'rainbow') && language) {
    +                language = language.toLowerCase();
    +
    +                _addClass(block, 'rainbow');
    +
    +                return _highlightBlockForLanguage(block.innerHTML, language, function(code) {
    +                    block.innerHTML = code;
    +
    +                    // reset the replacement arrays
    +                    replacements = {};
    +                    replacement_positions = {};
    +
    +                    // if you have a listener attached tell it that this block is now highlighted
    +                    if (onHighlight) {
    +                        onHighlight(block, language);
    +                    }
    +
    +                    // process the next block
    +                    setTimeout(function() {
    +                        _highlightCodeBlock(code_blocks, ++i, onComplete);
    +                    }, 0);
    +                });
    +            }
    +            return _highlightCodeBlock(code_blocks, ++i, onComplete);
    +        }
    +
    +        if (onComplete) {
    +            onComplete();
    +        }
    +    }
    +
    +    /**
    +     * start highlighting all the code blocks
    +     *
    +     * @returns void
    +     */
    +    function _highlight(node, onComplete) {
    +
    +        // the first argument can be an Event or a DOM Element
    +        // I was originally checking instanceof Event but that makes it break
    +        // when using mootools
    +        //
    +        // @see https://github.com/ccampbell/rainbow/issues/32
    +        //
    +        node = node && typeof node.getElementsByTagName == 'function' ? node : document;
    +
    +        var pre_blocks = node.getElementsByTagName('pre'),
    +            code_blocks = node.getElementsByTagName('code'),
    +            i,
    +            final_pre_blocks = [],
    +            final_code_blocks = [];
    +
    +        // first loop through all pre blocks to find which ones to highlight
    +        // also strip whitespace
    +        for (i = 0; i < pre_blocks.length; ++i) {
    +
    +            // strip whitespace around code tags when they are inside of a pre tag
    +            // this makes the themes look better because you can't accidentally
    +            // add extra linebreaks at the start and end
    +            //
    +            // when the pre tag contains a code tag then strip any extra whitespace
    +            // for example
    +            // 
    +            //      var foo = true;
    +            // 
    + // + // will become + //
    var foo = true;
    + // + // if you want to preserve whitespace you can use a pre tag on its own + // without a code tag inside of it + if (pre_blocks[i].getElementsByTagName('code').length) { + pre_blocks[i].innerHTML = pre_blocks[i].innerHTML.replace(/^\s+/, '').replace(/\s+$/, ''); + continue; + } + + // if the pre block has no code blocks then we are going to want to + // process it directly + final_pre_blocks.push(pre_blocks[i]); + } + + // @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript + // we are going to process all blocks + for (i = 0; i < code_blocks.length; ++i) { + final_code_blocks.push(code_blocks[i]); + } + + _highlightCodeBlock(final_code_blocks.concat(final_pre_blocks), 0, onComplete); + } + + /** + * public methods + */ + return { + + /** + * extends the language pattern matches + * + * @param {*} language name of language + * @param {*} patterns array of patterns to add on + * @param {boolean|null} bypass if true this will bypass the default language patterns + */ + extend: function(language, patterns, bypass) { + + // if there is only one argument then we assume that we want to + // extend the default language rules + if (arguments.length == 1) { + patterns = language; + language = DEFAULT_LANGUAGE; + } + + bypass_defaults[language] = bypass; + language_patterns[language] = patterns.concat(language_patterns[language] || []); + }, + + /** + * call back to let you do stuff in your app after a piece of code has been highlighted + * + * @param {Function} callback + */ + onHighlight: function(callback) { + onHighlight = callback; + }, + + /** + * method to set a global class that will be applied to all spans + * + * @param {string} class_name + */ + addClass: function(class_name) { + global_class = class_name; + }, + + /** + * starts the magic rainbow + * + * @returns void + */ + color: function() { + + // if you want to straight up highlight a string you can pass the string of code, + // the language, and a callback function + if (typeof arguments[0] == 'string') { + return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]); + } + + // if you pass a callback function then we rerun the color function + // on all the code and call the callback function on complete + if (typeof arguments[0] == 'function') { + return _highlight(0, arguments[0]); + } + + // otherwise we use whatever node you passed in with an optional + // callback function as the second parameter + _highlight(arguments[0], arguments[1]); + } + }; +}) (); + +/** + * adds event listener to start highlighting + */ +(function() { + if (document.addEventListener) { + return document.addEventListener('DOMContentLoaded', Rainbow.color, false); + } + window.attachEvent('onload', Rainbow.color); +}) (); + +// When using Google closure compiler in advanced mode some methods +// get renamed. This keeps a public reference to these methods so they can +// still be referenced from outside this library. +Rainbow["onHighlight"] = Rainbow.onHighlight; +Rainbow["addClass"] = Rainbow.addClass; diff --git a/vendor/assets/javascripts/ruby.js b/vendor/assets/javascripts/ruby.js new file mode 100644 index 00000000..a90fc960 --- /dev/null +++ b/vendor/assets/javascripts/ruby.js @@ -0,0 +1,227 @@ +/** + * Ruby patterns + * + * @author Matthew King + * @author Jesse Farmer + * @author actsasflinn + * @version 1.0.6 + */ + +Rainbow.extend('ruby', [ + /** + * __END__ DATA + */ + { + 'matches': { + 1: 'variable.language', + 2: { + 'language': null + } + }, + //find __END__ and consume remaining text + 'pattern': /^(__END__)\n((?:.*\n)*)/gm + }, + /** + * Strings + * 1. No support for multi-line strings + */ + { + 'name': 'string', + 'matches': { + 1: 'string.open', + 2: [{ + 'name': 'string.interpolation', + 'matches': { + 1: 'string.open', + 2: { + 'language': 'ruby' + }, + 3: 'string.close' + }, + 'pattern': /(\#\{)(.*?)(\})/g + }], + 3: 'string.close' + }, + 'pattern': /("|`)(.*?[^\\\1])?(\1)/g + }, + { + 'name': 'string', + 'pattern': /('|"|`)([^\\\1\n]|\\.)*?\1/g + }, + { + 'name': 'string', + 'pattern': /%[qQ](?=(\(|\[|\{|<|.)(.*?)(?:'|\)|\]|\}|>|\1))(?:\(\2\)|\[\2\]|\{\2\}|\<\2>|\1\2\1)/g + }, + /** + * Heredocs + * Heredocs of the form `<<'HTML' ... HTML` are unsupported. + */ + { + 'matches': { + 1: 'string', + 2: 'string', + 3: 'string' + }, + 'pattern': /(<<)(\w+).*?$([\s\S]*?^\2)/gm + }, + { + 'matches': { + 1: 'string', + 2: 'string', + 3: 'string' + }, + 'pattern': /(<<\-)(\w+).*?$([\s\S]*?\2)/gm + }, + /** + * Regular expressions + * Escaped delimiter (`/\//`) is unsupported. + */ + { + 'name': 'string.regexp', + 'matches': { + 1: 'string.regexp', + 2: { + 'name': 'string.regexp', + 'pattern': /\\(.){1}/g + }, + 3: 'string.regexp', + 4: 'string.regexp' + }, + 'pattern': /(\/)(.*?)(\/)([a-z]*)/g + }, + { + 'name': 'string.regexp', + 'matches': { + 1: 'string.regexp', + 2: { + 'name': 'string.regexp', + 'pattern': /\\(.){1}/g + }, + 3: 'string.regexp', + 4: 'string.regexp' + }, + 'pattern': /%r(?=(\(|\[|\{|<|.)(.*?)('|\)|\]|\}|>|\1))(?:\(\2\)|\[\2\]|\{\2\}|\<\2>|\1\2\1)([a-z]*)/g + }, + /** + * Comments + */ + { + 'name': 'comment', + 'pattern': /#.*$/gm + }, + { + 'name': 'comment', + 'pattern': /^\=begin[\s\S]*?\=end$/gm + }, + /** + * Symbols + */ + { + 'matches': { + 1: 'constant' + }, + 'pattern': /(\w+:)[^:]/g + }, + { + 'matches': { + 1: 'constant.symbol' + }, + 'pattern': /[^:](:(?:\w+|(?=['"](.*?)['"])(?:"\2"|'\2')))/g + }, + { + 'name': 'constant.numeric', + 'pattern': /\b(0x[\da-f]+|\d+)\b/g + }, + { + 'name': 'support.class', + 'pattern': /\b[A-Z]\w*(?=((\.|::)[A-Za-z]|\[))/g + }, + { + 'name': 'constant', + 'pattern': /\b[A-Z]\w*\b/g + }, + /** + * Keywords, variables, constants, and operators + * In Ruby some keywords are valid method names, e.g., MyClass#yield + * Don't mark those instances as "keywords" + */ + { + 'matches': { + 1: 'storage.class', + 2: 'entity.name.class', + 3: 'entity.other.inherited-class' + }, + 'pattern': /\s*(class)\s+((?:(?:::)?[A-Z]\w*)+)(?:\s+<\s+((?:(?:::)?[A-Z]\w*)+))?/g + }, + { + 'matches': { + 1: 'storage.module', + 2: 'entity.name.class' + }, + 'pattern': /\s*(module)\s+((?:(?:::)?[A-Z]\w*)+)/g + }, + { + 'name': 'variable.global', + 'pattern': /\$([a-zA-Z_]\w*)\b/g + }, + { + 'name': 'variable.class', + 'pattern': /@@([a-zA-Z_]\w*)\b/g + }, + { + 'name': 'variable.instance', + 'pattern': /@([a-zA-Z_]\w*)\b/g + }, + { + 'matches': { + 1: 'keyword.control' + }, + 'pattern': /[^\.]\b(BEGIN|begin|case|class|do|else|elsif|END|end|ensure|for|if|in|module|rescue|then|unless|until|when|while)\b(?![?!])/g + }, + { + 'matches': { + 1: 'keyword.control.pseudo-method' + }, + 'pattern': /[^\.]\b(alias|alias_method|break|next|redo|retry|return|super|undef|yield)\b(?![?!])|\bdefined\?|\bblock_given\?/g + }, + { + 'matches': { + 1: 'constant.language' + }, + 'pattern': /\b(nil|true|false)\b(?![?!])/g + }, + { + 'matches': { + 1: 'variable.language' + }, + 'pattern': /\b(__(FILE|LINE)__|self)\b(?![?!])/g + }, + { + 'matches': { + 1: 'keyword.special-method' + }, + 'pattern': /\b(require|gem|initialize|new|loop|include|extend|raise|attr_reader|attr_writer|attr_accessor|attr|catch|throw|private|module_function|public|protected)\b(?![?!])/g + }, + { + 'name': 'keyword.operator', + 'pattern': /\s\?\s|=|<<|<<=|%=|&=|\*=|\*\*=|\+=|\-=|\^=|\|{1,2}=|<<|<=>|<(?!<|=)|>(?!<|=|>)|<=|>=|===|==|=~|!=|!~|%|&|\*\*|\*|\+|\-|\/|\||~|>>/g + }, + { + 'matches': { + 1: 'keyword.operator.logical' + }, + 'pattern': /[^\.]\b(and|not|or)\b/g + }, + + /** + * Functions + * 1. No support for marking function parameters + */ + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(def)\s(.*?)(?=(\s|\())/g + } +], true); diff --git a/vendor/assets/stylesheets/github.css b/vendor/assets/stylesheets/github.css new file mode 100644 index 00000000..088f0657 --- /dev/null +++ b/vendor/assets/stylesheets/github.css @@ -0,0 +1,88 @@ +/** + * GitHub theme + * + * @author Craig Campbell + * @version 1.0.4 + */ +pre { + border: 1px solid #ccc; + word-wrap: break-word; + padding: 6px 10px; + line-height: 19px; + margin-bottom: 20px; +} + +code { + border: 1px solid #eaeaea; + margin: 0px 2px; + padding: 0px 5px; + font-size: 12px; +} + +pre code { + border: 0px; + padding: 0px; + margin: 0px; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} + +pre, code { + font-family: Consolas, 'Liberation Mono', Courier, monospace; + color: #333; + background: #f8f8f8; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +pre, pre code { + font-size: 13px; +} + +pre .comment { + color: #998; +} + +pre .support { + color: #0086B3; +} + +pre .tag, pre .tag-name { + color: navy; +} + +pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function { + font-weight: bold; +} + +pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace { + color: #333; +} + +pre .constant.numeric, pre .keyword.unit, pre .hex-color { + font-weight: normal; + color: #099; +} + +pre .entity.class { + color: #458; +} + +pre .entity.id, pre .entity.function { + color: #900; +} + +pre .attribute, pre .variable { + color: teal; +} + +pre .string, pre .support.value { + font-weight: normal; + color: #d14; +} + +pre .regexp { + color: #009926; +}