From cd433b2e65772f4393da53a77c33043fe619983f Mon Sep 17 00:00:00 2001 From: Will Read Date: Thu, 17 May 2012 23:41:21 -0700 Subject: [PATCH] Add wURL console to raddocs --- Gemfile.lock | 2 - example/Gemfile.lock | 2 - example/spec/acceptance/orders_spec.rb | 1 + example/spec/spec_helper.rb | 1 + example/test/fixtures/.gitkeep | 0 example/test/functional/.gitkeep | 0 example/test/integration/.gitkeep | 0 example/test/performance/browsing_test.rb | 12 - example/test/test_helper.rb | 13 - example/test/unit/.gitkeep | 0 example/vendor/assets/stylesheets/.gitkeep | 0 example/vendor/plugins/.gitkeep | 0 features/callbacks.feature | 4 +- features/html_documentation.feature | 4 +- gemfiles/minimum_dependencies | 1 - lib/rspec_api_documentation.rb | 1 - .../api_documentation.rb | 1 + lib/rspec_api_documentation/client_base.rb | 23 +- lib/rspec_api_documentation/curl.rb | 8 +- lib/rspec_api_documentation/html_writer.rb | 17 +- .../oauth2_mac_client.rb | 6 +- .../rack_test_client.rb | 6 +- lib/rspec_api_documentation/syntax.rb | 33 - lib/rspec_api_documentation/test_server.rb | 7 +- rspec_api_documentation.gemspec | 1 - spec/api_documentation_spec.rb | 2 +- spec/rack_test_client_spec.rb | 17 +- .../assets/img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes templates/assets/img/glyphicons-halflings.png | Bin 0 -> 13826 bytes templates/assets/javascripts/application.js | 250 + templates/assets/javascripts/codemirror.js | 3636 +++++++ templates/assets/javascripts/jquery-1-7-2.js | 9401 +++++++++++++++++ templates/assets/javascripts/jquery-base64.js | 189 + .../assets/javascripts/jquery-livequery.js | 226 + .../javascripts/jquery-ui-1-8-16-min.js | 791 ++ templates/assets/javascripts/mode/css/css.js | 124 + .../javascripts/mode/htmlmixed/htmlmixed.js | 85 + .../javascripts/mode/javascript/javascript.js | 361 + templates/assets/javascripts/mode/xml/xml.js | 325 + templates/assets/stylesheets/application.css | 68 + templates/assets/stylesheets/bootstrap.css | 4960 +++++++++ templates/assets/stylesheets/codemirror.css | 230 + .../html_example.mustache | 330 +- .../html_index.mustache | 46 +- 44 files changed, 20966 insertions(+), 218 deletions(-) delete mode 100644 example/test/fixtures/.gitkeep delete mode 100644 example/test/functional/.gitkeep delete mode 100644 example/test/integration/.gitkeep delete mode 100644 example/test/performance/browsing_test.rb delete mode 100644 example/test/test_helper.rb delete mode 100644 example/test/unit/.gitkeep delete mode 100644 example/vendor/assets/stylesheets/.gitkeep delete mode 100644 example/vendor/plugins/.gitkeep delete mode 100644 lib/rspec_api_documentation/syntax.rb create mode 100644 templates/assets/img/glyphicons-halflings-white.png create mode 100644 templates/assets/img/glyphicons-halflings.png create mode 100644 templates/assets/javascripts/application.js create mode 100644 templates/assets/javascripts/codemirror.js create mode 100644 templates/assets/javascripts/jquery-1-7-2.js create mode 100644 templates/assets/javascripts/jquery-base64.js create mode 100755 templates/assets/javascripts/jquery-livequery.js create mode 100755 templates/assets/javascripts/jquery-ui-1-8-16-min.js create mode 100644 templates/assets/javascripts/mode/css/css.js create mode 100644 templates/assets/javascripts/mode/htmlmixed/htmlmixed.js create mode 100644 templates/assets/javascripts/mode/javascript/javascript.js create mode 100644 templates/assets/javascripts/mode/xml/xml.js create mode 100644 templates/assets/stylesheets/application.css create mode 100644 templates/assets/stylesheets/bootstrap.css create mode 100644 templates/assets/stylesheets/codemirror.css diff --git a/Gemfile.lock b/Gemfile.lock index f291ed7f..5a485d0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,6 @@ PATH specs: rspec_api_documentation (0.5.2) activesupport (>= 3.0.0) - coderay (>= 1.0.7.rc1) i18n (>= 0.1.0) json (>= 1.4.6) mustache (>= 0.99.4) @@ -33,7 +32,6 @@ GEM xpath (~> 0.1.4) childprocess (0.3.2) ffi (~> 1.0.6) - coderay (1.0.7.rc1) crack (0.3.1) cucumber (1.2.0) builder (>= 2.1.2) diff --git a/example/Gemfile.lock b/example/Gemfile.lock index 987df9e6..48ee53ac 100644 --- a/example/Gemfile.lock +++ b/example/Gemfile.lock @@ -3,7 +3,6 @@ PATH specs: rspec_api_documentation (0.5.2) activesupport (>= 3.0.0) - coderay (>= 1.0.7.rc1) i18n (>= 0.1.0) json (>= 1.4.6) mustache (>= 0.99.4) @@ -44,7 +43,6 @@ GEM addressable (2.2.8) arel (2.2.1) builder (3.0.0) - coderay (1.0.7.rc1) coffee-rails (3.1.1) coffee-script (>= 2.2.0) railties (~> 3.1.0) diff --git a/example/spec/acceptance/orders_spec.rb b/example/spec/acceptance/orders_spec.rb index 2027f5b7..dddf1376 100644 --- a/example/spec/acceptance/orders_spec.rb +++ b/example/spec/acceptance/orders_spec.rb @@ -39,6 +39,7 @@ scope_parameters :order, :all example_request "Creating an order" do + explanation "First, create an order, then make a later request to get it back" response_body.should be_json_eql({ "name" => name, "paid" => paid, diff --git a/example/spec/spec_helper.rb b/example/spec/spec_helper.rb index 9b92c22a..61aae4ef 100644 --- a/example/spec/spec_helper.rb +++ b/example/spec/spec_helper.rb @@ -33,6 +33,7 @@ end RspecApiDocumentation.configure do |config| + config.docs_dir = Rails.root.join("public", "docs") config.format = [:html, :json] config.url_prefix = "docs/" config.curl_host = 'http://localhost:3000' diff --git a/example/test/fixtures/.gitkeep b/example/test/fixtures/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/test/functional/.gitkeep b/example/test/functional/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/test/integration/.gitkeep b/example/test/integration/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/test/performance/browsing_test.rb b/example/test/performance/browsing_test.rb deleted file mode 100644 index 3fea27b9..00000000 --- a/example/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] - # :output => 'tmp/performance', :formats => [:flat] } - - def test_homepage - get '/' - end -end diff --git a/example/test/test_helper.rb b/example/test/test_helper.rb deleted file mode 100644 index 8bf1192f..00000000 --- a/example/test/test_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' - -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/example/test/unit/.gitkeep b/example/test/unit/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/vendor/assets/stylesheets/.gitkeep b/example/vendor/assets/stylesheets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/vendor/plugins/.gitkeep b/example/vendor/plugins/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/features/callbacks.feature b/features/callbacks.feature index 0dc3f8b7..c6b346cf 100644 --- a/features/callbacks.feature +++ b/features/callbacks.feature @@ -66,7 +66,5 @@ Feature: Document callbacks | User-Agent | InterestingThingApp | And I should see the following request body: """ - { - "message": "Something interesting happened!" - } + {"message":"Something interesting happened!"} """ diff --git a/features/html_documentation.feature b/features/html_documentation.feature index c477f96d..955bbd65 100644 --- a/features/html_documentation.feature +++ b/features/html_documentation.feature @@ -80,7 +80,5 @@ Feature: Generate HTML documentation from test examples | Content-Length | 35 | And I should see the following response body: """ - { - "hello": "rspec_api_documentation" - } + {"hello":"rspec_api_documentation"} """ diff --git a/gemfiles/minimum_dependencies b/gemfiles/minimum_dependencies index 017c50ff..97d147c0 100644 --- a/gemfiles/minimum_dependencies +++ b/gemfiles/minimum_dependencies @@ -8,7 +8,6 @@ gem "i18n", "0.3.6" gem "mustache", "0.99.4" gem "webmock", "1.8.0" gem "json", "1.4.6" -gem "coderay", "1.0.7.rc1" gem "rack-test", "0.5.5" gem "rack-oauth2", "0.14.4" diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index 595a5dda..fecbc947 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -18,7 +18,6 @@ module RspecApiDocumentation autoload :Index autoload :ClientBase autoload :Headers - autoload :Syntax end autoload :DSL diff --git a/lib/rspec_api_documentation/api_documentation.rb b/lib/rspec_api_documentation/api_documentation.rb index c1947c20..ccd9cf0b 100644 --- a/lib/rspec_api_documentation/api_documentation.rb +++ b/lib/rspec_api_documentation/api_documentation.rb @@ -14,6 +14,7 @@ def clear_docs FileUtils.rm_rf(docs_dir, :secure => true) end FileUtils.mkdir_p(docs_dir) + FileUtils.cp_r(File.join(configuration.template_path, "assets"), docs_dir) end def document_example(rspec_example) diff --git a/lib/rspec_api_documentation/client_base.rb b/lib/rspec_api_documentation/client_base.rb index 56afa5b8..c510348c 100644 --- a/lib/rspec_api_documentation/client_base.rb +++ b/lib/rspec_api_documentation/client_base.rb @@ -1,7 +1,6 @@ module RspecApiDocumentation class ClientBase < Struct.new(:context, :options) include Headers - include Syntax delegate :example, :app, :to => :context delegate :metadata, :to => :example @@ -40,13 +39,15 @@ def document_example(method, path, params) request_metadata[:request_method] = method request_metadata[:request_path] = path - request_metadata[:request_body] = highlight_syntax(request_body, content_type, true) - request_metadata[:request_headers] = format_headers(request_headers) - request_metadata[:request_query_parameters] = format_query_hash(query_hash) + request_metadata[:request_body] = request_body.empty? ? nil : request_body + request_metadata[:request_headers] = request_headers + request_metadata[:request_query_parameters] = query_hash + request_metadata[:request_content_type] = request_content_type request_metadata[:response_status] = status request_metadata[:response_status_text] = Rack::Utils::HTTP_STATUS_CODES[status] - request_metadata[:response_body] = highlight_syntax(response_body, response_headers['Content-Type']) - request_metadata[:response_headers] = format_headers(response_headers) + request_metadata[:response_body] = response_body.empty? ? nil : response_body + request_metadata[:response_headers] = response_headers + request_metadata[:response_content_type] = response_content_type request_metadata[:curl] = Curl.new(method, path, request_body, request_headers) metadata[:requests] ||= [] @@ -56,18 +57,12 @@ def document_example(method, path, params) def query_hash strings = query_string.split("&") arrays = strings.map do |segment| - segment.split("=") + k,v = segment.split("=") + [k, CGI.unescape(v)] end Hash[arrays] end - def format_query_hash(query_hash) - return if query_hash.blank? - query_hash.map do |key, value| - "#{key}: #{CGI.unescape(value)}" - end.join("\n") - end - def headers(method, path, params) if options && options[:headers] options[:headers] diff --git a/lib/rspec_api_documentation/curl.rb b/lib/rspec_api_documentation/curl.rb index a797d052..606fdfab 100644 --- a/lib/rspec_api_documentation/curl.rb +++ b/lib/rspec_api_documentation/curl.rb @@ -10,19 +10,19 @@ def output(config_host) end def post - "curl #{url} #{post_data} -X POST #{headers}" + "curl \"#{url}\" #{post_data} -X POST #{headers}" end def get - "curl #{url}#{get_data} -X GET #{headers}" + "curl \"#{url}#{get_data}\" -X GET #{headers}" end def put - "curl #{url} #{post_data} -X PUT #{headers}" + "curl \"#{url}\" #{post_data} -X PUT #{headers}" end def delete - "curl #{url} -X DELETE #{headers}" + "curl \"#{url}\" #{post_data} -X DELETE #{headers}" end def url diff --git a/lib/rspec_api_documentation/html_writer.rb b/lib/rspec_api_documentation/html_writer.rb index 1ec0e85c..74a30b3d 100644 --- a/lib/rspec_api_documentation/html_writer.rb +++ b/lib/rspec_api_documentation/html_writer.rb @@ -69,7 +69,14 @@ def filename end def requests - super.map do |hash| + super.collect do |hash| + hash[:request_headers_hash] = hash[:request_headers].collect { |k, v| {:name => k, :value => v} } + hash[:request_headers_text] = format_hash(hash[:request_headers]) + hash[:request_path_no_query] = hash[:request_path].split('?').first + hash[:request_query_parameters_text] = format_hash(hash[:request_query_parameters]) + hash[:request_query_parameters_hash] = hash[:request_query_parameters].collect { |k, v| {:name => k, :value => v} } if hash[:request_query_parameters].present? + hash[:response_headers_text] = format_hash(hash[:response_headers]) + hash[:response_status] = hash[:response_status].to_s + " " + Rack::Utils::HTTP_STATUS_CODES[hash[:response_status]].to_s if @host hash[:curl] = hash[:curl].output(@host) if hash[:curl].is_a? RspecApiDocumentation::Curl else @@ -82,5 +89,13 @@ def requests def url_prefix configuration.url_prefix end + + private + def format_hash(hash = {}) + return "" unless hash.present? + hash.collect do |k, v| + "#{k}: #{v}" + end.join("\n") + end end end diff --git a/lib/rspec_api_documentation/oauth2_mac_client.rb b/lib/rspec_api_documentation/oauth2_mac_client.rb index 2c6eb841..1d897dae 100644 --- a/lib/rspec_api_documentation/oauth2_mac_client.rb +++ b/lib/rspec_api_documentation/oauth2_mac_client.rb @@ -32,10 +32,14 @@ def response_body last_response.body end - def content_type + def request_content_type last_request.content_type end + def response_content_type + last_response.content_type + end + protected def do_request(method, path, params) diff --git a/lib/rspec_api_documentation/rack_test_client.rb b/lib/rspec_api_documentation/rack_test_client.rb index 49ea1e2b..9ea53ffa 100644 --- a/lib/rspec_api_documentation/rack_test_client.rb +++ b/lib/rspec_api_documentation/rack_test_client.rb @@ -24,10 +24,14 @@ def response_body last_response.body end - def content_type + def request_content_type last_request.content_type end + def response_content_type + last_response.content_type + end + protected def do_request(method, path, params) diff --git a/lib/rspec_api_documentation/syntax.rb b/lib/rspec_api_documentation/syntax.rb deleted file mode 100644 index 9a9737b5..00000000 --- a/lib/rspec_api_documentation/syntax.rb +++ /dev/null @@ -1,33 +0,0 @@ -require "coderay" - -module RspecApiDocumentation - module Syntax - private - - def highlight_syntax(body, content_type, is_query_string = false) - return if body.blank? - begin - case content_type - when /json/ - CodeRay.scan(JSON.pretty_generate(JSON.parse(body)), :json).div - when /html/ - CodeRay.scan(body, :html).div - when /javascript/ - CodeRay.scan(body, :java_script).div - when /xml/ - CodeRay.scan(body, :xml).div - else - body = prettify_request_body(body) if is_query_string - "
#{body}
" - end - rescue Exception => e - "
#{e.inspect}
" - end - end - - def prettify_request_body(string) - return if string.blank? - CGI.unescape(string.split("&").join("\n")) - end - end -end diff --git a/lib/rspec_api_documentation/test_server.rb b/lib/rspec_api_documentation/test_server.rb index e95ddcdc..f80a3f73 100644 --- a/lib/rspec_api_documentation/test_server.rb +++ b/lib/rspec_api_documentation/test_server.rb @@ -1,7 +1,6 @@ module RspecApiDocumentation class TestServer < Struct.new(:context) include Headers - include Syntax delegate :example, :to => :context delegate :metadata, :to => :example @@ -18,10 +17,10 @@ def call(env) request_metadata = {} - request_metadata[:request_method] = request_method + request_metadata[:request_method] = @request_method request_metadata[:request_path] = env["PATH_INFO"] - request_metadata[:request_body] = highlight_syntax(request_body, request_headers["Content-Type"], true) - request_metadata[:request_headers] = format_headers(@request_headers) + request_metadata[:request_body] = @request_body + request_metadata[:request_headers] = @request_headers metadata[:requests] ||= [] metadata[:requests] << request_metadata diff --git a/rspec_api_documentation.gemspec b/rspec_api_documentation.gemspec index 3512af15..4e9105ba 100644 --- a/rspec_api_documentation.gemspec +++ b/rspec_api_documentation.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |s| s.add_runtime_dependency "mustache", ">= 0.99.4" s.add_runtime_dependency "webmock", ">= 1.7.0" s.add_runtime_dependency "json", ">= 1.4.6" - s.add_runtime_dependency "coderay", ">= 1.0.7.rc1" s.add_development_dependency "fakefs" s.add_development_dependency "sinatra" diff --git a/spec/api_documentation_spec.rb b/spec/api_documentation_spec.rb index 5336c39e..7fbd7704 100644 --- a/spec/api_documentation_spec.rb +++ b/spec/api_documentation_spec.rb @@ -15,7 +15,7 @@ test_file = configuration.docs_dir.join("test") FileUtils.mkdir_p configuration.docs_dir FileUtils.touch test_file - + FileUtils.stub(:cp_r) subject.clear_docs File.directory?(configuration.docs_dir).should be_true diff --git a/spec/rack_test_client_spec.rb b/spec/rack_test_client_spec.rb index 86b1ff03..e68c7903 100644 --- a/spec/rack_test_client_spec.rb +++ b/spec/rack_test_client_spec.rb @@ -127,14 +127,15 @@ class StubApp < Sinatra::Base metadata[:request_method].should eq("POST") metadata[:request_path].should eq("/greet?query=test+query") metadata[:request_body].should be_present - metadata[:request_headers].should match(/^Content-Type: application\/json/) - metadata[:request_headers].should match(/^X-Custom-Header: custom header value$/) - metadata[:request_query_parameters].should eq("query: test query") + metadata[:request_headers].should include({'Content-Type' => 'application/json;charset=utf-8'}) + metadata[:request_headers].should include({'X-Custom-Header' => 'custom header value'}) + metadata[:request_query_parameters].should == {"query" => "test query"} + metadata[:request_content_type].should match(/application\/json/) metadata[:response_status].should eq(200) - metadata[:response_status_text].should eq("OK") metadata[:response_body].should be_present - metadata[:response_headers].should match(/^Content-Type: application\/json/) - metadata[:response_headers].should match(/^Content-Length: 17$/) + metadata[:response_headers]['Content-Type'].should match(/application\/json/) + metadata[:response_headers]['Content-Length'].should == '17' + metadata[:response_content_type].should match(/application\/json/) metadata[:curl].should eq(RspecApiDocumentation::Curl.new("POST", "/greet?query=test+query", post_data, {"Content-Type" => "application/json;charset=utf-8", "X-Custom-Header" => "custom header value", "Host" => "example.org", "Cookie" => ""})) end @@ -151,8 +152,8 @@ class StubApp < Sinatra::Base context "when post data is nil" do let(:post_data) { } - it "should not nil out request_body" do - example.metadata[:requests].first[:request_body].should eq(nil) + it "should nil out request_body" do + example.metadata[:requests].first[:request_body].should be_nil end end end diff --git a/templates/assets/img/glyphicons-halflings-white.png b/templates/assets/img/glyphicons-halflings-white.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 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 B7G?kd literal 0 HcmV?d00001 diff --git a/templates/assets/img/glyphicons-halflings.png b/templates/assets/img/glyphicons-halflings.png new file mode 100644 index 0000000000000000000000000000000000000000..79bc568c21395d5a5ceab62fadb04457094b2ac7 GIT binary patch literal 13826 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 literal 0 HcmV?d00001 diff --git a/templates/assets/javascripts/application.js b/templates/assets/javascripts/application.js new file mode 100644 index 00000000..a2b3f5f9 --- /dev/null +++ b/templates/assets/javascripts/application.js @@ -0,0 +1,250 @@ +var headers = ["Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Authorization", + "Cache-Control", + "Connection", + "Cookie", + "Content-Length", + "Content-MD5", + "Content-Type", + "Date", + "Expect", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Max-Forwards", + "Pragma", + "Proxy-Authorization", + "Range", + "Referer", + "TE", + "Upgrade", + "User-Agent", + "Via", + "Warning"]; + +function mirror(textarea, contentType, options) { + $textarea = $(textarea); + if ($textarea.val() != '') { + if(contentType.indexOf('json') >= 0) { + $textarea.val(JSON.stringify(JSON.parse($textarea.val()), undefined, 2)); + options.json = true; + options.mode = 'javascript'; + } else if (contentType.indexOf('javascript') >= 0) { + options.mode = 'javascript'; + } else if (contentType.indexOf('xml') >= 0) { + options.mode = 'xml'; + } else { + options.mode = 'htmlmixed'; + } + } + return CodeMirror.fromTextArea(textarea, options); +}; + +function Wurl(wurlForm) { + this.$wurlForm = $(wurlForm); + var self = this; + + this.requestBodyMirror = mirror(this.$wurlForm.find('.post_body textarea')[0], $('.request.content_type', this.$wurlForm).val(), {}) + this.responseBodyMirror = mirror(this.$wurlForm.find('.response.body textarea')[0], $('.response.content_type', this.$wurlForm).val(), { "readOnly": true, "lineNumbers":true}); + + $('.give_it_a_wurl', this.$wurlForm).click(function (event) { + event.preventDefault(); + self.sendWurl(); + }); + $('.add_header', this.$wurlForm).click(function () { + self.addInputs('header'); + }); + + $('.add_param', this.$wurlForm).click(function () { + self.addInputs('param'); + }); + + $('.delete_header', this.$wurlForm).live('click', function (e) { + self.deleteHeader(this); + }); + + $('.delete_param', this.$wurlForm).live('click', function (e) { + self.deleteParam(this); + }); + + $(".trash_headers", this.$wurlForm).click(function () { + self.trashHeaders(); + }); + + $(".trash_queries", self.$wurlForm).click(function () { + self.trashQueries(); + }); + + $('.header_pair input.value', this.$wurlForm).live('focusin', (function () { + if ($('.header_pair:last input', self.$wurlForm).val() != "") { + self.addInputs('header'); + } + })); + + $('.param_pair input.value', this.$wurlForm).live('focusin', (function () { + if ($('.param_pair:last input', self.$wurlForm).val() != "") { + self.addInputs('param'); + } + })); + + $('.url select', this.$wurlForm).change(function () { + self.updateBodyInput(); + }); + + $(".header_pair input.key", this.$wurlForm).livequery(function () { + $(this).autocomplete({source:headers}); + }); + + $(".clear_fields", this.$wurlForm).click(function () { + $("input[type=text], textarea", self.$wurlForm).val(""); + self.trashHeaders(); + self.trashQueries(); + }); + + this.addInputs = function (type) { + var $fields = $('.' + type + '_pair', this.$wurlForm).first().clone(); + $fields.children('input').val("").attr('disabled', false); + $fields.hide().appendTo(this.$wurlForm.find('.' + type + 's')).slideDown('fast'); + }; + + this.deleteHeader = function (element) { + var $fields = $(element).closest(".header_pair"); + $fields.slideUp(function () { + $fields.remove(); + }); + }; + + this.deleteParam = function (element) { + var $fields = $(element).closest(".param_pair"); + $fields.slideUp(function () { + $paramFields.remove(); + }); + }; + + this.trashHeaders = function () { + $(".header_pair:visible", self.$wurlForm).each(function (i, element) { + $(element).slideUp(function () { + $(element).remove(); + }); + }); + this.addInputs('header'); + }; + + this.trashQueries = function () { + $(".param_pair:visible", self.$wurlForm).each(function (i, element) { + $(element).slideUp(function () { + $(element).remove(); + }); + }); + this.addInputs('param'); + }; + + this.updateBodyInput = function () { + var method = $('#wurl_request_method', self.$wurlForm).val(); + if ($.inArray(method, ["PUT", "POST", "DELETE"]) > -1) { + $('#wurl_request_body', self.$wurlForm).attr('disabled', false).removeClass('textarea_disabled'); + } else { + $('#wurl_request_body', self.$wurlForm).attr('disabled', true).addClass('textarea_disabled'); + } + }; + this.updateBodyInput(); + + this.makeBasicAuth = function () { + var user = $('#wurl_basic_auth_user', this.$wurlForm).val(); + var password = $('#wurl_basic_auth_password', this.$wurlForm).val(); + var token = user + ':' + password; + var hash = $.base64.encode(token); + return "Basic " + hash; + }; + + this.queryParams = function () { + var toReturn = []; + $(".param_pair:visible", self.$wurlForm).each(function (i, element) { + paramKey = $(element).find('input.key').val(); + paramValue = $(element).find('input.value').val(); + if (paramKey.length && paramValue.length) { + toReturn.push(paramKey + '=' + paramValue); + } + }); + return toReturn.join("&"); + }; + + this.getData = function () { + var method = $('#wurl_request_method', self.$wurlForm).val(); + if ($.inArray(method, ["PUT", "POST", "DELETE"]) > -1) { + self.requestBodyMirror.save(); + return self.requestBodyMirror.getValue(); + } else { + return self.queryParams(); + } + }; + + this.url = function () { + var url = $('#wurl_request_url', self.$wurlForm).val(); + var method = $('#wurl_request_method', self.$wurlForm).val(); + var params = self.queryParams(); + if ($.inArray(method, ["PUT", "POST", "DELETE"]) > -1 && params.length) { + url += "?" + params; + } + return url[0] == '/' ? url : '/' + url; + }; + + this.sendWurl = function () { + $.ajax({ + beforeSend:function (req) { + $(".header_pair:visible", self.$wurlForm).each(function (i, element) { + headerKey = $(element).find('input.key').val(); + headerValue = $(element).find('input.value').val(); + req.setRequestHeader(headerKey, headerValue); + }); + req.setRequestHeader('Authorization', self.makeBasicAuth()); + }, + type:$('#wurl_request_method', self.$wurlForm).val(), + url:this.url(), + data:this.getData(), + complete:function (jqXHR) { + var $status = $('.response.status', self.$wurlForm); + $status.html(jqXHR.status + ' ' + jqXHR.statusText); + + $('.response.headers', self.$wurlForm).html(jqXHR.getAllResponseHeaders()); + + contentType = jqXHR.getResponseHeader("content-type"); + if (contentType.indexOf('json') >= 0 && jqXHR.responseText.length > 1) { + self.responseBodyMirror.setValue(JSON.stringify(JSON.parse(jqXHR.responseText), undefined, 2)); + self.responseBodyMirror.setOption('mode', 'javascript'); + self.responseBodyMirror.setOption('json', true); + } else if (contentType.indexOf('javascript') >= 0) { + self.responseBodyMirror.setValue(jqXHR.responseText); + self.responseBodyMirror.setOption('mode', 'javascript'); + } else if (contentType.indexOf('xml') >= 0) { + self.responseBodyMirror.setValue(jqXHR.responseText); + self.responseBodyMirror.setOption('mode', 'xml'); + } else { + self.responseBodyMirror.setValue(jqXHR.responseText); + self.responseBodyMirror.setOption('mode', 'htmlmixed'); + } + $('.response', self.$wurlForm).effect("highlight", {}, 3000); + $('html,body').animate({ scrollTop:$('a.response_anchor', self.$wurlForm).offset().top }, { duration:'slow', easing:'swing'}); + } + }); + }; +} + +$(function () { + $('.wurl_form').each(function (index, wurlForm) { + wurl = new Wurl(wurlForm); + }); + + var $textAreas = $('.request.body textarea'); + $textAreas.each(function(i, textarea) { + var contentType = $(textarea).parents('div.request').find('.request.content_type').val(); + mirror(textarea, contentType, {"readOnly":true, "lineNumbers": true}); + }); +}); diff --git a/templates/assets/javascripts/codemirror.js b/templates/assets/javascripts/codemirror.js new file mode 100644 index 00000000..f7a7bc43 --- /dev/null +++ b/templates/assets/javascripts/codemirror.js @@ -0,0 +1,3636 @@ +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +var CodeMirror = (function () { + // This is the function that produces an editor instance. Its + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + // The element in which the editor lives. + var wrapper = document.createElement("div"); + wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); + // This mess creates the base DOM structure for the editor. + wrapper.innerHTML = + '
' + // Wraps and hides input textarea + '
' + + '
' + + '
' + // Set to the height of the text, causes scrolling + '
' + // Moved around its parent to cover visible view + '
' + + // Provides positioning relative to (visible) text origin + '
' + + '
' + + '
 
' + // Absolutely positioned blinky cursor + '
' + // DIVs containing the selection and the actual code + '
'; + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + // I've never seen more elegant code in my life. + var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, + scroller = wrapper.lastChild, code = scroller.firstChild, + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, + lineDiv = selectionDiv.nextSibling; + themeChanged(); + keyMapChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) lineSpace.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; + + // Check for problem with IE innerHTML not working when we have a + // P (or similar) parent node. + try { + stringWidth("x"); + } + catch (e) { + if (e.message.match(/runtime/i)) + e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); + throw e; + } + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from:{line:0, ch:0}, to:{line:0, ch:0}, inverted:false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + overwrite = false, suppressEdits = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a bracket has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = "", maxWidth; + var tabCache = {}; + + // Initialize the content. + operation(function () { + setValue(options.value || ""); + updateInput = false; + })(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "selectstart", e_preventDefault); + // Gecko browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for Gecko. + if (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", function () { + lastScrollPos = scroller.scrollTop; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + }); + connect(window, "resize", function () { + updateDisplay(true); + }); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + if (options.dragDrop) { + connect(lineSpace, "dragstart", onDragStart); + function drag_(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_stop(e); + } + + connect(scroller, "dragenter", drag_); + connect(scroller, "dragover", drag_); + connect(scroller, "drop", operation(onDrop)); + } + connect(scroller, "paste", function () { + focusInput(); + fastPoll(); + }); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function () { + if (!options.readOnly) replaceSelection(""); + })); + + // Needed to handle Tab key in KHTML + if (khtml) connect(code, "mouseup", function () { + if (document.activeElement == input) input.blur(); + focusInput(); + }); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; + try { + hasFocus = (document.activeElement == input); + } catch (e) { + } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) { + return l >= 0 && l < doc.size; + } + + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue:getValue, + setValue:operation(setValue), + getSelection:getSelection, + replaceSelection:operation(replaceSelection), + focus:function () { + window.focus(); + focusInput(); + onFocus(); + fastPoll(); + }, + setOption:function (option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") { + onBlur(); + input.blur(); + } + else if (option == "readOnly" && !value) { + resetInput(true); + } + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") updateDisplay(true); + else if (option == "keyMap") keyMapChanged(); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") { + gutterChanged(); + updateDisplay(true); + } + }, + getOption:function (option) { + return options[option]; + }, + undo:operation(undo), + redo:operation(redo), + indentLine:operation(function (n, dir) { + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); + }), + indentSelection:operation(indentSelected), + historySize:function () { + return {undo:history.done.length, redo:history.undone.length}; + }, + clearHistory:function () { + history = new History(); + }, + matchBrackets:operation(function () { + matchBrackets(true); + }), + getTokenAt:operation(function (pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), + getStateAfter:function (line) { + line = clipLine(line == null ? doc.size - 1 : line); + return getStateBefore(line + 1); + }, + cursorCoords:function (start, mode) { + if (start == null) start = sel.inverted; + return this.charCoords(start ? sel.from : sel.to, mode); + }, + charCoords:function (pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); + }, + coordsChar:function (coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText:operation(markText), + setBookmark:setBookmark, + findMarksAt:findMarksAt, + setMarker:operation(addGutterMarker), + clearMarker:operation(removeGutterMarker), + setLineClass:operation(setLineClass), + hideLine:operation(function (h) { + return setLineHidden(h, true); + }), + showLine:operation(function (h) { + return setLineHidden(h, false); + }), + onDeleteLine:function (line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo:lineInfo, + addWidget:function (pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + code.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = code.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount:function () { + return doc.size; + }, + clipPos:clipPos, + getCursor:function (start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected:function () { + return !posEq(sel.from, sel.to); + }, + setCursor:operation(function (line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection:operation(function (from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine:function (line) { + if (isLine(line)) return getLine(line).text; + }, + getLineHandle:function (line) { + if (isLine(line)) return getLine(line); + }, + setLine:operation(function (line, text) { + if (isLine(line)) replaceRange(text, {line:line, ch:0}, {line:line, ch:getLine(line).text.length}); + }), + removeLine:operation(function (line) { + if (isLine(line)) replaceRange("", {line:line, ch:0}, clipPos({line:line + 1, ch:0})); + }), + replaceRange:operation(replaceRange), + getRange:function (from, to) { + return getRange(clipPos(from), clipPos(to)); + }, + + triggerOnKeyDown:operation(onKeyDown), + execCommand:function (cmd) { + return commands[cmd](instance); + }, + // Stuff used by commands, probably not much use to outside code. + moveH:operation(moveH), + deleteH:operation(deleteH), + moveV:operation(moveV), + toggleOverwrite:function () { + if (overwrite) { + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; + } + }, + + posFromIndex:function (off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function (line) { + var sz = line.text.length + 1; + if (sz > off) { + ch = off; + return true; + } + off -= sz; + ++lineNo; + }); + return clipPos({line:lineNo, ch:ch}); + }, + indexFromPos:function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo:function (x, y) { + if (x != null) scroller.scrollLeft = x; + if (y != null) scroller.scrollTop = y; + updateDisplay([]); + }, + + operation:function (f) { + return operation(f)(); + }, + compoundChange:function (f) { + return compoundChange(f); + }, + refresh:function () { + updateDisplay(true); + if (scroller.scrollHeight > lastScrollPos) + scroller.scrollTop = lastScrollPos; + }, + getInputField:function () { + return input; + }, + getWrapperElement:function () { + return wrapper; + }, + getScrollerElement:function () { + return scroller; + }, + getGutterElement:function () { + return gutter; + } + }; + + function getLine(n) { + return getLineAt(doc, n); + } + + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function setValue(code) { + var top = {line:0, ch:0}; + updateLines(top, {line:doc.size - 1, ch:getLine(doc.size - 1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + + function getValue() { + var text = []; + doc.iter(0, doc.size, function (line) { + text.push(line.text); + }); + return text.join("\n"); + } + + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == code && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko && !mac) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + return; + } + // For button 1, if it was clicked inside the editor + // (posFromMouse returning non-null), we have to adjust the + // selection. + if (!start) { + if (e_target(e) == scroller) e_preventDefault(e); + return; + } + + if (!focused) onFocus(); + + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + e_preventDefault(e); + setTimeout(focusInput, 20); + return selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time:now, pos:start}; + e_preventDefault(e); + return selectWordAt(start); + } else { + lastClick = {time:now, pos:start}; + } + + var last = start, going; + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start)) { + // Let the drag handler handle this. + if (webkit) lineSpace.draggable = true; + function dragEnd(e2) { + if (webkit) lineSpace.draggable = false; + draggingText = false; + up(); + drop(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + } + + var up = connect(document, "mouseup", operation(dragEnd), true); + var drop = connect(scroller, "drop", operation(dragEnd), true); + draggingText = true; + // IE's approach to draggable + if (lineSpace.dragDrop) lineSpace.dragDrop(); + return; + } + e_preventDefault(e); + setCursor(start.line, start.ch, true); + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + setSelectionUser(start, cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function () { + extend(e); + }), 150); + } + } + + function done(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) setSelectionUser(start, cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); + up(); + } + + var move = connect(document, "mousemove", operation(function (e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); + }), true); + var up = connect(document, "mouseup", operation(done), true); + } + + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + var start = posFromMouse(e); + if (!start) return; + lastDoubleClick = {time:+new Date, pos:start}; + e_preventDefault(e); + selectWordAt(start); + } + + function onDrop(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e.preventDefault(); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + function loadFile(file, i) { + var reader = new FileReader; + reader.onload = function () { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function () { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + } + + var n = files.length, text = Array(n), read = 0; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } + else { + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + compoundChange(function () { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + }); + } + } + catch (e) { + } + } + } + + function onDragStart(e) { + var txt = getSelection(); + e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (gecko || chrome) { + var img = document.createElement('img'); + img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image + e.dataTransfer.setDragImage(img, 0, 0); + } + } + + function doHandleBinding(bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } catch (e) { + if (e != Pass) throw e; + return false; + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + return true; + } + + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function () { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + + var stopped = false; + + function stop() { + stopped = true; + } + + if (e_prop(e, "shiftKey")) { + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function (b) { + return doHandleBinding(b, true); + }, stop) + || lookupKey(name, options.extraKeys, options.keyMap, function (b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }, stop); + } else { + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); + } + if (stopped) handled = false; + if (handled) { + e_preventDefault(e); + restartBlink(); + if (ie) { + e.oldKeyCode = e.keyCode; + e.keyCode = 0; + } + } + return handled; + } + + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, function (b) { + return doHandleBinding(b, true); + }); + if (handled) { + e_preventDefault(e); + restartBlink(); + } + return handled; + } + + var lastStoppedKey = null, maybeTransition; + + function onKeyDown(e) { + if (!focused) onFocus(); + if (ie && e.keyCode == 27) { + e.returnValue = false; + } + if (pollingFast) { + if (readInput()) pollingFast = false; + } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); + // IE does strange things with escape. + setShift(code == 16 || e_prop(e, "shiftKey")); + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(e); + if (window.opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); + } + } + + function onKeyPress(e) { + if (pollingFast) readInput(); + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (window.opera && keyCode == lastStoppedKey) { + lastStoppedKey = null; + e_preventDefault(e); + return; + } + if (((window.opera && !e.which) || khtml) && handleKeyBinding(e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function () { + indentLine(sel.to.line, "smart"); + }), 75); + } + if (handleCharBinding(e, ch)) return; + fastPoll(); + } + + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly == "nocursor") return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + wrapper.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); + } + slowPoll(); + restartBlink(); + } + + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + if (bracketHighlighted) + operation(function () { + if (bracketHighlighted) { + bracketHighlighted(); + bracketHighlighted = null; + } + })(); + wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function () { + if (!focused) shiftSelecting = null; + }, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + if (history) { + var old = []; + doc.iter(from.line, to.line + 1, function (line) { + old.push(line.text); + }); + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + updateLinesNoUndo(from, to, newText, selFrom, selTo); + } + + function unredoHelper(from, to) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { + var change = set[i]; + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function (line) { + replaced.push(line.text); + }); + out.push({start:change.start, added:change.old.length, old:replaced}); + var pos = clipPos({line:change.start + change.old.length - 1, + ch:editEnd(replaced[replaced.length - 1], change.old[change.old.length - 1])}); + updateLinesNoUndo({line:change.start, ch:0}, {line:end - 1, ch:getLine(end - 1).text.length}, change.old, pos, pos); + } + updateInput = true; + to.push(out); + } + + function undo() { + unredoHelper(history.done, history.undone); + } + + function redo() { + unredoHelper(history.undone, history.done); + } + + function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + var recomputeMaxLength = false, maxLineLength = maxLine.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line + 1, function (line) { + if (line.text.length == maxLineLength) { + recomputeMaxLength = true; + return true; + } + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + // First adjust the line structure, taking some care to leave highlighting intact. + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (newText.length == 1) + firstLine.replace(from.ch, to.ch, newText[0]); + else { + lastLine = firstLine.split(to.ch, newText[newText.length - 1]); + firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); + } + } else if (newText.length == 1) { + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, ""); + firstLine.append(lastLine); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, newText[newText.length - 1]); + firstLine.fixMarkEnds(lastLine); + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); + doc.iter(from.line, from.line + newText.length, function (line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, from.line + newText.length, function (line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; + maxLineLength = l.length; + maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; + maxLine = ""; + maxWidth = null; + doc.iter(0, doc.size, function (line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; + maxLine = l; + } + }); + } + } + + // Add these lines to the work array, so that they will be + // highlighted. Adjust work lines if lines were added/removed. + var newWork = [], lendiff = newText.length - nlines - 1; + for (var i = 0, l = work.length; i < l; ++i) { + var task = work[i]; + if (task < from.line) newWork.push(task); + else if (task > to.line) newWork.push(task + lendiff); + } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); + work = newWork; + startWorker(100); + // Remember that these lines changed, for updating the display + changes.push({from:from.line, to:to.line + 1, diff:lendiff}); + var changeObj = {from:from, to:to, text:newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) { + } + cur.next = changeObj; + } else textChanged = changeObj; + + // Update the selection + function updateLine(n) { + return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff; + } + + setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + + // Make sure the scroll-size div has the correct height. + if (scroller.clientHeight) + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += code[code.length - 1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line:line, ch:ch}; + } + + var end; + replaceRange1(code, from, to, function (end1) { + end = end1; + return {from:adjustPos(sel.from), to:adjustPos(sel.to)}; + }); + return end; + } + + function replaceSelection(code, collapse) { + replaceRange1(splitLines(code), sel.from, sel.to, function (end) { + if (collapse == "end") return {from:end, to:end}; + else if (collapse == "start") return {from:sel.from, to:sel.from}; + else return {from:sel.from, to:end}; + }); + } + + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : code[code.length - 1].length; + var newSel = computeSel({line:from.line + code.length - 1, ch:endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function (line) { + code.push(line.text); + }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join("\n"); + } + + function getSelection() { + return getRange(sel.from, sel.to); + } + + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function () { + startOperation(); + readInput(); + if (focused) slowPoll(); + endOperation(); + }); + } + + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + startOperation(); + var changed = readInput(); + if (!changed && !missed) { + missed = true; + poll.set(60, p); + } + else { + pollingFast = false; + slowPoll(); + } + endOperation(); + } + + poll.set(20, p); + } + + // Previnput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + var prevInput = ""; + + function readInput() { + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line:sel.from.line, ch:sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line:sel.to.line, ch:Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + prevInput = text; + return true; + } + + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + selectInput(input); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (options.readOnly != "nocursor") input.focus(); + } + + function scrollEditorIntoView() { + if (!cursor.getBoundingClientRect) return; + var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + } + + function scrollCursorIntoView() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return scrollIntoView(x, cursor.y, x, cursor.yBot); + } + + function scrollIntoView(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(); + y1 += pt; + y2 += pt; + x1 += pl; + x2 += pl; + var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; + if (y1 < screentop) { + scroller.scrollTop = Math.max(0, y1); + scrolled = true; + } + else if (y2 > screentop + screen) { + scroller.scrollTop = y2 - screen; + scrolled = true; + } + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + var atLeft = x1 < gutterw + pl + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); + scrolled = true; + } + else if (x2 > screenw + screenleft - 3) { + scroller.scrollLeft = x2 + 10 - screenw; + scrolled = true; + if (x2 > code.clientWidth) result = false; + } + if (scrolled && options.onScroll) options.onScroll(instance); + return result; + } + + function visibleLines() { + var lh = textHeight(), top = scroller.scrollTop - paddingTop(); + var fromHeight = Math.max(0, Math.floor(top / lh)); + var toHeight = Math.ceil((top + scroller.clientHeight) / lh); + return {from:lineAtHeight(doc, fromHeight), + to:lineAtHeight(doc, toHeight)}; + } + + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + var visible = visibleLines(); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([ + {from:showingFrom, to:showingTo, domStart:0} + ], changes); + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) { + range.domStart += (from - range.from); + range.from = from; + } + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (intactLines == to - from && from == showingFrom && to == showingTo) return; + intact.sort(function (a, b) { + return a.domStart - b.domStart; + }); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + // Position the mover div to align with the lines it's supposed + // to be showing (which will cover the visible display) + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; + showingTo = to; + displayOffset = heightAtLine(doc, from); + mover.style.top = (displayOffset * th) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + function checkHeights() { + maxWidth = scroller.clientWidth; + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function (line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + if (heightChanged) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + return heightChanged; + } + + if (options.lineWrapping) { + checkHeights(); + } else { + if (maxWidth == null) maxWidth = stringWidth(maxLine); + if (maxWidth > scroller.clientWidth) { + lineSpace.style.width = maxWidth + "px"; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = ""; + code.style.width = scroller.scrollWidth + "px"; + } else { + lineSpace.style.width = code.style.width = ""; + } + } + + gutter.style.display = gutterDisplay; + if (different || gutterDirty) { + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. + updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); + } + updateSelection(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + function computeIntact(intact, changes) { + for (var i = 0, l = changes.length || 0; i < l; ++i) { + var change = changes[i], intact2 = [], diff = change.diff || 0; + for (var j = 0, l2 = intact.length; j < l2; ++j) { + var range = intact[j]; + if (change.to <= range.from && change.diff) + intact2.push({from:range.from + diff, to:range.to + diff, + domStart:range.domStart}); + else if (change.to <= range.from || change.from >= range.to) + intact2.push(range); + else { + if (change.from > range.from) + intact2.push({from:range.from, to:change.from, domStart:range.domStart}); + if (change.to < range.to) + intact2.push({from:change.to + diff, to:range.to + diff, + domStart:range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) { + curNode = killNode(curNode); + domPos++; + } + for (var j = 0, e = cur.to - cur.from; j < e; ++j) { + curNode = curNode.nextSibling; + domPos++; + } + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var scratch = document.createElement("div"); + doc.iter(from, to, function (line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var html = scratch.innerHTML = "
";
+                    else {
+                        var html = ''
+                            + line.getHTML(makeTab) + '';
+                        // Kludge to make sure the styled element lies behind the selection (by z-index)
+                        if (line.bgClassName)
+                            html = '
 
' + html + "
"; + } + scratch.innerHTML = html; + lineDiv.insertBefore(scratch.firstChild, curNode); + } else { + curNode = curNode.nextSibling; + } + ++j; + }); + } + + function updateGutter() { + if (!options.gutter && !options.lineNumbers) return; + var hText = mover.offsetHeight, hEditor = scroller.clientHeight; + gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; + var html = [], i = showingFrom, normalNode; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function (line) { + if (line.hidden) { + html.push("
");
+                } else {
+                    var marker = line.gutterMarker;
+                    var text = options.lineNumbers ? i + options.firstLineNumber : null;
+                    if (marker && marker.text)
+                        text = marker.text.replace("%N%", text != null ? text : "");
+                    else if (text == null)
+                        text = "\u00a0";
+                    html.push((marker && marker.style ? '
' : "
"), text);
+                    for (var j = 1; j < line.height; ++j) html.push("
 "); + html.push("
"); + if (!marker) normalNode = i; + } + ++i; + }); + gutter.style.display = "none"; + gutterText.innerHTML = html.join(""); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } + gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; + lineSpace.style.marginLeft = gutter.offsetWidth + "px"; + gutterDirty = false; + return resized; + } + + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; + cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + + function add(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; + html += '
'; + } + + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + selectionDiv.innerHTML = html; + cursor.style.display = "none"; + selectionDiv.style.display = ""; + } + } + + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } + + function setSelectionUser(from, to) { + var sh = shiftSelecting && clipPos(shiftSelecting); + if (sh) { + if (posLess(sh, from)) from = sh; + else if (posLess(to, sh)) to = sh; + } + setSelection(from, to); + userSelChange = true; + } + + // Update the selection. Last two args are only used by + // updateLines, since they have to be expressed in the line + // numbers before the update. + function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) { + oldFrom = sel.from.line; + oldTo = sel.to.line; + } + if (posEq(sel.from, from) && posEq(sel.to, to)) return; + if (posLess(to, from)) { + var tmp = to; + to = from; + from = tmp; + } + + // Skip over hidden lines. + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + + if (posEq(from, to)) sel.inverted = false; + else if (posEq(from, sel.to)) sel.inverted = false; + else if (posEq(to, sel.from)) sel.inverted = true; + + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function () { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line:no, ch:0}, {line:no, ch:oldLine.text.length}); + } + }, 10)); + } + } + + sel.from = from; + sel.to = to; + selectionChanged = true; + } + + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line:lNo, ch:ch}; + } + lNo += dir; + } + } + + var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } + + function setCursor(line, ch, user) { + var pos = clipPos({line:line, ch:ch || 0}); + (user ? setSelectionUser : setSelection)(pos, pos); + } + + function clipLine(n) { + return Math.max(0, Math.min(n, doc.size - 1)); + } + + function clipPos(pos) { + if (pos.line < 0) return {line:0, ch:0}; + if (pos.line >= doc.size) return {line:doc.size - 1, ch:getLine(doc.size - 1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; + if (ch == null || ch > linelen) return {line:pos.line, ch:linelen}; + else if (ch < 0) return {line:pos.line, ch:0}; + else return pos; + } + + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { + line = l; + lineObj = lo; + return true; + } + } + } + + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else ch += dir; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (; ;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) { + if (dir < 0) { + dir = 1; + moveOnce(); + } + break; + } + if (dir > 0) if (!moveOnce()) break; + } + } + return {line:line, ch:ch}; + } + + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; + } + + var goalColumn = null; + + function moveV(dir, unit) { + var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + else if (unit == "line") dist = textHeight(); + var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") scroller.scrollTop += localCoords(target, true).y - pos.y; + setCursor(target.line, target.ch, true); + goalColumn = pos.x; + } + + function selectWordAt(pos) { + var line = getLine(pos.line).text; + var start = pos.ch, end = pos.ch; + while (start > 0 && isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && isWordChar(line.charAt(end))) ++end; + setSelectionUser({line:pos.line, ch:start}, {line:pos.line, ch:end}); + } + + function selectLine(line) { + setSelectionUser({line:line, ch:0}, clipPos({line:line + 1, ch:0})); + } + + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); + } + + function indentLine(n, how) { + if (!how) how = "add"; + if (how == "smart") { + if (!mode.indent) how = "prev"; + else var state = getStateBefore(n); + } + + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "prev") { + if (n) indentation = getLine(n - 1).indentation(options.tabSize); + else indentation = 0; + } + else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + else if (how == "add") indentation = curSpace + options.indentUnit; + else if (how == "subtract") indentation = curSpace - options.indentUnit; + indentation = Math.max(0, indentation); + var diff = indentation - curSpace; + + if (!diff) { + if (sel.from.line != n && sel.to.line != n) return; + var indentString = curSpaceString; + } + else { + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) { + pos += options.tabSize; + indentString += "\t"; + } + while (pos < indentation) { + ++pos; + indentString += " "; + } + } + + replaceRange(indentString, {line:n, ch:0}, {line:n, ch:curSpaceString.length}); + } + + function loadMode() { + mode = CodeMirror.getMode(options, options.mode); + doc.iter(0, doc.size, function (line) { + line.stateAfter = null; + }); + work = [0]; + startWorker(); + } + + function gutterChanged() { + var visible = options.gutter || options.lineNumbers; + gutter.style.display = visible ? "" : "none"; + if (visible) gutterDirty = true; + else lineDiv.parentNode.style.marginLeft = 0; + } + + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function (line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.width = code.style.width = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + maxWidth = null; + maxLine = ""; + doc.iter(0, doc.size, function (line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.text.length > maxLine.length) maxLine = line.text; + }); + } + changes.push({from:0, to:doc.size}); + } + + function makeTab(col) { + var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; + if (cached) return cached; + for (var str = '', i = 0; i < w; ++i) str += " "; + return (tabCache[w] = {html:str + "", width:w}); + } + + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } + + function TextMarker() { + this.set = []; + } + + TextMarker.prototype.clear = operation(function () { + var min = Infinity, max = -Infinity; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + if (!mk || !line.parent) continue; + var lineN = lineNo(line); + min = Math.min(min, lineN); + max = Math.max(max, lineN); + for (var j = 0; j < mk.length; ++j) + if (mk[j].marker == this) mk.splice(j--, 1); + } + if (min != Infinity) + changes.push({from:min, to:max + 1}); + }); + TextMarker.prototype.find = function () { + var from, to; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + for (var j = 0; j < mk.length; ++j) { + var mark = mk[j]; + if (mark.marker == this) { + if (mark.from != null || mark.to != null) { + var found = lineNo(line); + if (found != null) { + if (mark.from != null) from = {line:found, ch:mark.from}; + if (mark.to != null) to = {line:found, ch:mark.to}; + } + } + } + } + } + return {from:from, to:to}; + }; + + function markText(from, to, className) { + from = clipPos(from); + to = clipPos(to); + var tm = new TextMarker(); + if (!posLess(from, to)) return tm; + function add(line, from, to, className) { + getLine(line).addMark(new MarkedText(from, to, className, tm)); + } + + if (from.line == to.line) add(from.line, from.ch, to.ch, className); + else { + add(from.line, from.ch, null, className); + for (var i = from.line + 1, e = to.line; i < e; ++i) + add(i, null, null, className); + add(to.line, null, to.ch, className); + } + changes.push({from:from.line, to:to.line + 1}); + return tm; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var bm = new Bookmark(pos.ch); + getLine(pos.line).addMark(bm); + return bm; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], marked = getLine(pos.line).marked; + if (!marked) return markers; + for (var i = 0, e = marked.length; i < e; ++i) { + var m = marked[i]; + if ((m.from == null || m.from <= pos.ch) && + (m.to == null || m.to >= pos.ch)) + markers.push(m.marker || m); + } + return markers; + } + + function addGutterMarker(line, text, className) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = {text:text, style:className}; + gutterDirty = true; + return line; + } + + function removeGutterMarker(line) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = null; + gutterDirty = true; + } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from:no, to:no + 1}); + else return null; + return line; + } + + function setLineClass(handle, className, bgClassName) { + return changeLine(handle, function (line) { + if (line.className != className || line.bgClassName != bgClassName) { + line.className = className; + line.bgClassName = bgClassName; + return true; + } + }); + } + + function setLineHidden(handle, hidden) { + return changeLine(handle, function (line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line:fline, ch:0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line:tline, ch:0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } + + function lineInfo(line) { + if (typeof line == "number") { + if (!isLine(line)) return null; + var n = line; + line = getLine(line); + if (!line) return null; + } + else { + var n = lineNo(line); + if (n == null) return null; + } + var marker = line.gutterMarker; + return {line:n, handle:line, text:line.text, markerText:marker && marker.text, + markerClass:marker && marker.style, lineClass:line.className, bgClass:line.bgClassName}; + } + + function stringWidth(str) { + measure.innerHTML = "
x
"; + measure.firstChild.firstChild.firstChild.nodeValue = str; + return measure.firstChild.firstChild.offsetWidth || 10; + } + + // These are used to go from pixel positions to character + // positions, taking varying character widths into account. + function charFromX(line, x) { + if (x <= 0) return 0; + var lineObj = getLine(line), text = lineObj.text; + + function getX(len) { + return measureLine(lineObj, len).left; + } + + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil(x / charWidth())); + for (; ;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else { + toX = estX; + to = estimated; + break; + } + } + if (x > toX) return to; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); + estX = getX(estimated); + if (estX < x) { + from = estimated; + fromX = estX; + } + // Do a binary search between these bounds. + for (; ;) { + if (to - from <= 1) return (toX - x > x - fromX) ? from : to; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) { + to = middle; + toX = middleX; + } + else { + from = middle; + fromX = middleX; + } + } + } + + var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16); + + function measureLine(line, ch) { + if (ch == 0) return {top:0, left:0}; + var wbr = options.lineWrapping && ch < line.text.length && + spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1)); + measure.innerHTML = "
" + line.getHTML(makeTab, ch, tempId, wbr) + "
"; + var elt = document.getElementById(tempId); + var top = elt.offsetTop, left = elt.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = document.createElement("span"); + backup.innerHTML = "x"; + elt.parentNode.insertBefore(backup, elt.nextSibling); + top = backup.offsetTop; + } + return {top:top, left:left}; + } + + function localCoords(pos, inLineWrap) { + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x:x, y:y, yBot:y + lh}; + } + + // Coords must be lineSpace-local + function coordsChar(x, y) { + if (y < 0) y = 0; + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line:doc.size - 1, ch:getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line:lineNo, ch:0}; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (; ;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else { + toX = estX; + to = estimated; + break; + } + } + if (x > toX) return {line:lineNo, ch:to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); + estX = getX(estimated); + if (estX < x) { + from = estimated; + fromX = estX; + } + // Do a binary search between these bounds. + for (; ;) { + if (to - from <= 1) return {line:lineNo, ch:(toX - x > x - fromX) ? from : to}; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) { + to = middle; + toX = middleX; + } + else { + from = middle; + fromX = middleX; + } + } + } + + function pageCoords(pos) { + var local = localCoords(pos, true), off = eltOffset(lineSpace); + return {x:off.left + local.x, y:off.top + local.y, yBot:off.top + local.yBot}; + } + + var cachedHeight, cachedHeightFor, measureText; + + function textHeight() { + if (measureText == null) { + measureText = "
";
+                for (var i = 0; i < 49; ++i) measureText += "x
"; + measureText += "x
"; + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + measure.innerHTML = measureText; + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + measure.innerHTML = ""; + return cachedHeight; + } + + var cachedWidth, cachedWidthFor = 0; + + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + return (cachedWidth = stringWidth("x")); + } + + function paddingTop() { + return lineSpace.offsetTop; + } + + function paddingLeft() { + return lineSpace.offsetLeft; + } + + function posFromMouse(e, liberal) { + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { + x = e.clientX; + y = e.clientY; + } catch (e) { + return null; + } + // This is a mess of a heuristic to try and determine whether a + // scroll-bar was clicked or not, and to return null if one was + // (and !liberal). + if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) + return null; + var offL = eltOffset(lineSpace, true); + return coordsChar(x - offL.left, y - offL.top); + } + + function onContextMenu(e) { + var pos = posFromMouse(e), scrollPos = scroller.scrollTop; + if (!pos || window.opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(setCursor)(pos.line, pos.ch); + + var oldCSS = input.style.cssText; + inputDiv.style.position = "absolute"; + input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + leaveInputAlone = true; + var val = input.value = getSelection(); + focusInput(); + selectInput(input); + function rehide() { + var newVal = splitLines(input.value).join("\n"); + if (newVal != val) operation(replaceSelection)(newVal, "end"); + inputDiv.style.position = "relative"; + input.style.cssText = oldCSS; + if (ie_lt9) scroller.scrollTop = scrollPos; + leaveInputAlone = false; + resetInput(true); + slowPoll(); + } + + if (gecko) { + e_stop(e); + var mouseup = connect(window, "mouseup", function () { + mouseup(); + setTimeout(rehide, 20); + }, true); + } else { + setTimeout(rehide, 50); + } + } + + // Cursor-blinking + function restartBlink() { + clearInterval(blinker); + var on = true; + cursor.style.visibility = ""; + blinker = setInterval(function () { + cursor.style.visibility = (on = !on) ? "" : "hidden"; + }, 650); + } + + var matching = {"(":")>", ")":"(<", "[":"]>", "]":"[<", "{":"}>", "}":"{<"}; + + function matchBrackets(autoclear) { + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return; + var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; + for (var off = pos + 1, i = 0, e = st.length; i < e; i += 2) + if ((off -= st[i].length) <= 0) { + var style = st[i + 1]; + break; + } + + var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; + + function scan(line, from, to) { + if (!line.text) return; + var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; + for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2 * d) { + var text = st[i]; + if (st[i + 1] != null && st[i + 1] != style) { + pos += d * text.length; + continue; + } + for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos += d) { + if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { + var match = matching[cur]; + if (match.charAt(1) == ">" == forward) stack.push(cur); + else if (stack.pop() != match.charAt(0)) return {pos:pos, match:false}; + else if (!stack.length) return {pos:pos, match:true}; + } + } + } + } + + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i += d) { + var line = getLine(i), first = i == head.line; + var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); + if (found) break; + } + if (!found) found = {pos:null, match:false}; + var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + var one = markText({line:head.line, ch:pos}, {line:head.line, ch:pos + 1}, style), + two = found.pos != null && markText({line:i, ch:found.pos}, {line:i, ch:found.pos + 1}, style); + var clear = operation(function () { + one.clear(); + two && two.clear(); + }); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(n) { + var minindent, minline; + for (var search = n, lim = n - 40; search > lim; --search) { + if (search == 0) return 0; + var line = getLine(search - 1); + if (line.stateAfter) return search; + var indented = line.indentation(options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(n) { + var start = findStartLine(n), state = start && getLine(start - 1).stateAfter; + if (!state) state = startState(mode); + else state = copyState(mode, state); + doc.iter(start, n, function (line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + if (start < n) changes.push({from:start, to:n}); + if (n < doc.size && !getLine(n).stateAfter) work.push(n); + return state; + } + + function highlightLines(start, end) { + var state = getStateBefore(start); + doc.iter(start, end, function (line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + } + + function highlightWorker() { + var end = +new Date + options.workTime; + var foundWork = work.length; + while (work.length) { + if (!getLine(showingFrom).stateAfter) var task = showingFrom; + else var task = work.pop(); + if (task >= doc.size) continue; + var start = findStartLine(task), state = start && getLine(start - 1).stateAfter; + if (state) state = copyState(mode, state); + else state = startState(mode); + + var unchanged = 0, compare = mode.compareStates, realChange = false, + i = start, bail = false; + doc.iter(i, doc.size, function (line) { + var hadState = line.stateAfter; + if (+new Date > end) { + work.push(i); + startWorker(options.workDelay); + if (realChange) changes.push({from:task, to:i + 1}); + return (bail = true); + } + var changed = line.highlight(mode, state, options.tabSize); + if (changed) realChange = true; + line.stateAfter = copyState(mode, state); + var done = null; + if (compare) { + var same = hadState && compare(hadState, state); + if (same != Pass) done = !!same; + } + if (done == null) { + if (changed !== false || !hadState) unchanged = 0; + else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) + done = true; + } + if (done) return true; + ++i; + }); + if (bail) return; + if (realChange) changes.push({from:task, to:i + 1}); + } + if (foundWork && options.onHighlightComplete) + options.onHighlightComplete(instance); + } + + function startWorker(time) { + if (!work.length) return; + highlight.set(time, operation(highlightWorker)); + } + + // Operations are used to wrap changes in such a way that each + // change won't have to update the cursor and display (which would + // be awkward, slow, and error-prone), but instead updates are + // batched and then all combined and executed at once. + function startOperation() { + updateInput = userSelChange = textChanged = null; + changes = []; + selectionChanged = false; + callbacks = []; + } + + function endOperation() { + var reScroll = false, updated; + if (selectionChanged) reScroll = !scrollCursorIntoView(); + if (changes.length) updated = updateDisplay(changes, true); + else { + if (selectionChanged) updateSelection(); + if (gutterDirty) updateGutter(); + } + if (reScroll) scrollCursorIntoView(); + if (selectionChanged) { + scrollEditorIntoView(); + restartBlink(); + } + + if (focused && !leaveInputAlone && + (updateInput === true || (updateInput !== false && selectionChanged))) + resetInput(userSelChange); + + if (selectionChanged && options.matchBrackets) + setTimeout(operation(function () { + if (bracketHighlighted) { + bracketHighlighted(); + bracketHighlighted = null; + } + if (posEq(sel.from, sel.to)) matchBrackets(false); + }), 20); + var tc = textChanged, cbs = callbacks; // these can be reset by callbacks + if (selectionChanged && options.onCursorActivity) + options.onCursorActivity(instance); + if (tc && options.onChange && instance) + options.onChange(instance, tc); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); + } + + var nestedOperation = 0; + + function operation(f) { + return function () { + if (!nestedOperation++) startOperation(); + try { + var result = f.apply(this, arguments); + } + finally { + if (!--nestedOperation) endOperation(); + } + return result; + }; + } + + function compoundChange(f) { + history.startCompound(); + try { + return f(); + } finally { + history.endCompound(); + } + } + + for (var ext in extensions) + if (extensions.propertyIsEnumerable(ext) && + !instance.propertyIsEnumerable(ext)) + instance[ext] = extensions[ext]; + return instance; + } // (end of function CodeMirror) + + // The default configuration options. + CodeMirror.defaults = { + value:"", + mode:null, + theme:"default", + indentUnit:2, + indentWithTabs:false, + smartIndent:true, + tabSize:4, + keyMap:"default", + extraKeys:null, + electricChars:true, + autoClearEmptyLines:false, + onKeyEvent:null, + onDragEvent:null, + lineWrapping:false, + lineNumbers:false, + gutter:false, + fixedGutter:false, + firstLineNumber:1, + readOnly:false, + dragDrop:true, + onChange:null, + onCursorActivity:null, + onGutterClick:null, + onHighlightComplete:null, + onUpdate:null, + onFocus:null, onBlur:null, onScroll:null, + matchBrackets:false, + workTime:100, + workDelay:200, + pollInterval:100, + undoDepth:40, + tabindex:null, + autofocus:null + }; + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + CodeMirror.defineMode = function (name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } + modes[name] = mode; + }; + CodeMirror.defineMIME = function (mime, spec) { + mimeModes[mime] = spec; + }; + CodeMirror.resolveMode = function (spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + spec = mimeModes[spec]; + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name:spec}; + else return spec || {name:"null"}; + }; + CodeMirror.getMode = function (options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + return mfactory(options, spec); + }; + CodeMirror.listModes = function () { + var list = []; + for (var m in modes) + if (modes.propertyIsEnumerable(m)) list.push(m); + return list; + }; + CodeMirror.listMIMEs = function () { + var list = []; + for (var m in mimeModes) + if (mimeModes.propertyIsEnumerable(m)) list.push({mime:m, mode:mimeModes[m]}); + return list; + }; + + var extensions = CodeMirror.extensions = {}; + CodeMirror.defineExtension = function (name, func) { + extensions[name] = func; + }; + + var commands = CodeMirror.commands = { + selectAll:function (cm) { + cm.setSelection({line:0, ch:0}, {line:cm.lineCount() - 1}); + }, + killLine:function (cm) { + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); + if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line:from.line + 1, ch:0}); + else cm.replaceRange("", from, sel ? to : {line:from.line}); + }, + deleteLine:function (cm) { + var l = cm.getCursor().line; + cm.replaceRange("", {line:l, ch:0}, {line:l}); + }, + undo:function (cm) { + cm.undo(); + }, + redo:function (cm) { + cm.redo(); + }, + goDocStart:function (cm) { + cm.setCursor(0, 0, true); + }, + goDocEnd:function (cm) { + cm.setSelection({line:cm.lineCount() - 1}, null, true); + }, + goLineStart:function (cm) { + cm.setCursor(cm.getCursor().line, 0, true); + }, + goLineStartSmart:function (cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd:function (cm) { + cm.setSelection({line:cm.getCursor().line}, null, true); + }, + goLineUp:function (cm) { + cm.moveV(-1, "line"); + }, + goLineDown:function (cm) { + cm.moveV(1, "line"); + }, + goPageUp:function (cm) { + cm.moveV(-1, "page"); + }, + goPageDown:function (cm) { + cm.moveV(1, "page"); + }, + goCharLeft:function (cm) { + cm.moveH(-1, "char"); + }, + goCharRight:function (cm) { + cm.moveH(1, "char"); + }, + goColumnLeft:function (cm) { + cm.moveH(-1, "column"); + }, + goColumnRight:function (cm) { + cm.moveH(1, "column"); + }, + goWordLeft:function (cm) { + cm.moveH(-1, "word"); + }, + goWordRight:function (cm) { + cm.moveH(1, "word"); + }, + delCharLeft:function (cm) { + cm.deleteH(-1, "char"); + }, + delCharRight:function (cm) { + cm.deleteH(1, "char"); + }, + delWordLeft:function (cm) { + cm.deleteH(-1, "word"); + }, + delWordRight:function (cm) { + cm.deleteH(1, "word"); + }, + indentAuto:function (cm) { + cm.indentSelection("smart"); + }, + indentMore:function (cm) { + cm.indentSelection("add"); + }, + indentLess:function (cm) { + cm.indentSelection("subtract"); + }, + insertTab:function (cm) { + cm.replaceSelection("\t", "end"); + }, + defaultTab:function (cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, + transposeChars:function (cm) { + var cur = cm.getCursor(), line = cm.getLine(cur.line); + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + {line:cur.line, ch:cur.ch - 1}, {line:cur.line, ch:cur.ch + 1}); + }, + newlineAndIndent:function (cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite:function (cm) { + cm.toggleOverwrite(); + } + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left":"goCharLeft", "Right":"goCharRight", "Up":"goLineUp", "Down":"goLineDown", + "End":"goLineEnd", "Home":"goLineStartSmart", "PageUp":"goPageUp", "PageDown":"goPageDown", + "Delete":"delCharRight", "Backspace":"delCharLeft", "Tab":"defaultTab", "Shift-Tab":"indentAuto", + "Enter":"newlineAndIndent", "Insert":"toggleOverwrite" + }; + // Note that the save and find-related commands aren't defined by + // default. Unknown commands are simply ignored. + keyMap.pcDefault = { + "Ctrl-A":"selectAll", "Ctrl-D":"deleteLine", "Ctrl-Z":"undo", "Shift-Ctrl-Z":"redo", "Ctrl-Y":"redo", + "Ctrl-Home":"goDocStart", "Alt-Up":"goDocStart", "Ctrl-End":"goDocEnd", "Ctrl-Down":"goDocEnd", + "Ctrl-Left":"goWordLeft", "Ctrl-Right":"goWordRight", "Alt-Left":"goLineStart", "Alt-Right":"goLineEnd", + "Ctrl-Backspace":"delWordLeft", "Ctrl-Delete":"delWordRight", "Ctrl-S":"save", "Ctrl-F":"find", + "Ctrl-G":"findNext", "Shift-Ctrl-G":"findPrev", "Shift-Ctrl-F":"replace", "Shift-Ctrl-R":"replaceAll", + "Ctrl-[":"indentLess", "Ctrl-]":"indentMore", + fallthrough:"basic" + }; + keyMap.macDefault = { + "Cmd-A":"selectAll", "Cmd-D":"deleteLine", "Cmd-Z":"undo", "Shift-Cmd-Z":"redo", "Cmd-Y":"redo", + "Cmd-Up":"goDocStart", "Cmd-End":"goDocEnd", "Cmd-Down":"goDocEnd", "Alt-Left":"goWordLeft", + "Alt-Right":"goWordRight", "Cmd-Left":"goLineStart", "Cmd-Right":"goLineEnd", "Alt-Backspace":"delWordLeft", + "Ctrl-Alt-Backspace":"delWordRight", "Alt-Delete":"delWordRight", "Cmd-S":"save", "Cmd-F":"find", + "Cmd-G":"findNext", "Shift-Cmd-G":"findPrev", "Cmd-Alt-F":"replace", "Shift-Cmd-Alt-F":"replaceAll", + "Cmd-[":"indentLess", "Cmd-]":"indentMore", + fallthrough:["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + keyMap.emacsy = { + "Ctrl-F":"goCharRight", "Ctrl-B":"goCharLeft", "Ctrl-P":"goLineUp", "Ctrl-N":"goLineDown", + "Alt-F":"goWordRight", "Alt-B":"goWordLeft", "Ctrl-A":"goLineStart", "Ctrl-E":"goLineEnd", + "Ctrl-V":"goPageUp", "Shift-Ctrl-V":"goPageDown", "Ctrl-D":"delCharRight", "Ctrl-H":"delCharLeft", + "Alt-D":"delWordRight", "Alt-Backspace":"delWordLeft", "Ctrl-K":"killLine", "Ctrl-T":"transposeChars" + }; + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; + } + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; + } + + if (extraMap && lookup(extraMap)) return true; + return lookup(map); + } + + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + + CodeMirror.fromTextArea = function (textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + if (options.autofocus == null && textarea.getAttribute("autofocus") != null) + options.autofocus = true; + + function save() { + textarea.value = instance.getValue(); + } + + if (textarea.form) { + // Deplorable hack to make the submit method do the right thing. + var rmSubmit = connect(textarea.form, "submit", save, true); + if (typeof textarea.form.submit == "function") { + var realSubmit = textarea.form.submit; + + function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + } + + textarea.form.submit = wrappedSubmit; + } + } + + textarea.style.display = "none"; + var instance = CodeMirror(function (node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + instance.save = save; + instance.getTextArea = function () { + return textarea; + }; + instance.toTextArea = function () { + save(); + textarea.parentNode.removeChild(instance.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + rmSubmit(); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return instance; + }; + + // Utility functions for working with state. Exported because modes + // sometimes need to do this. + function copyState(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + } + + CodeMirror.copyState = copyState; + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + } + + CodeMirror.startState = startState; + + // The character stream used by a mode's parser. + function StringStream(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + } + + StringStream.prototype = { + eol:function () { + return this.pos >= this.string.length; + }, + sol:function () { + return this.pos == 0; + }, + peek:function () { + return this.string.charAt(this.pos); + }, + next:function () { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat:function (match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) { + ++this.pos; + return ch; + } + }, + eatWhile:function (match) { + var start = this.pos; + while (this.eat(match)) { + } + return this.pos > start; + }, + eatSpace:function () { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd:function () { + this.pos = this.string.length; + }, + skipTo:function (ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) { + this.pos = found; + return true; + } + }, + backUp:function (n) { + this.pos -= n; + }, + column:function () { + return countColumn(this.string, this.start, this.tabSize); + }, + indentation:function () { + return countColumn(this.string, null, this.tabSize); + }, + match:function (pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + function cased(str) { + return caseInsensitive ? str.toLowerCase() : str; + } + + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } + else { + var match = this.string.slice(this.pos).match(pattern); + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current:function () { + return this.string.slice(this.start, this.pos); + } + }; + CodeMirror.StringStream = StringStream; + + function MarkedText(from, to, className, marker) { + this.from = from; + this.to = to; + this.style = className; + this.marker = marker; + } + + MarkedText.prototype = { + attach:function (line) { + this.marker.set.push(line); + }, + detach:function (line) { + var ix = indexOf(this.marker.set, line); + if (ix > -1) this.marker.set.splice(ix, 1); + }, + split:function (pos, lenBefore) { + if (this.to <= pos && this.to != null) return null; + var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; + var to = this.to == null ? null : this.to - pos + lenBefore; + return new MarkedText(from, to, this.style, this.marker); + }, + dup:function () { + return new MarkedText(null, null, this.style, this.marker); + }, + clipTo:function (fromOpen, from, toOpen, to, diff) { + if (fromOpen && to > this.from && (to < this.to || this.to == null)) + this.from = null; + else if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; + if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) + this.to = null; + else if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; + }, + isDead:function () { + return this.from != null && this.to != null && this.from >= this.to; + }, + sameSet:function (x) { + return this.marker == x.marker; + } + }; + + function Bookmark(pos) { + this.from = pos; + this.to = pos; + this.line = null; + } + + Bookmark.prototype = { + attach:function (line) { + this.line = line; + }, + detach:function (line) { + if (this.line == line) this.line = null; + }, + split:function (pos, lenBefore) { + if (pos < this.from) { + this.from = this.to = (this.from - pos) + lenBefore; + return this; + } + }, + isDead:function () { + return this.from > this.to; + }, + clipTo:function (fromOpen, from, toOpen, to, diff) { + if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { + this.from = 0; + this.to = -1; + } else if (this.from > from) { + this.from = this.to = Math.max(to, this.from) + diff; + } + }, + sameSet:function (x) { + return false; + }, + find:function () { + if (!this.line || !this.line.parent) return null; + return {line:lineNo(this.line), ch:this.from}; + }, + clear:function () { + if (this.line) { + var found = indexOf(this.line.marked, this); + if (found != -1) this.line.marked.splice(found, 1); + this.line = null; + } + } + }; + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + function Line(text, styles) { + this.styles = styles || [text, null]; + this.text = text; + this.height = 1; + this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null; + this.stateAfter = this.parent = this.hidden = null; + } + + Line.inheritMarks = function (text, orig) { + var ln = new Line(text), mk = orig && orig.marked; + if (mk) { + for (var i = 0; i < mk.length; ++i) { + if (mk[i].to == null && mk[i].style) { + var newmk = ln.marked || (ln.marked = []), mark = mk[i]; + var nmark = mark.dup(); + newmk.push(nmark); + nmark.attach(ln); + } + } + } + return ln; + } + Line.prototype = { + // Replace a piece of a line, keeping the styles around it intact. + replace:function (from, to_, text) { + var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; + copyStyles(0, from, this.styles, st); + if (text) st.push(text, null); + copyStyles(to, this.text.length, this.styles, st); + this.styles = st; + this.text = this.text.slice(0, from) + text + this.text.slice(to); + this.stateAfter = null; + if (mk) { + var diff = text.length - (to - from); + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + mark.clipTo(from == null, from || 0, to_ == null, to, diff); + if (mark.isDead()) { + mark.detach(this); + mk.splice(i--, 1); + } + } + } + }, + // Split a part off a line, keeping styles and markers intact. + split:function (pos, textBefore) { + var st = [textBefore, null], mk = this.marked; + copyStyles(pos, this.text.length, this.styles, st); + var taken = new Line(textBefore + this.text.slice(pos), st); + if (mk) { + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + var newmark = mark.split(pos, textBefore.length); + if (newmark) { + if (!taken.marked) taken.marked = []; + taken.marked.push(newmark); + newmark.attach(taken); + if (newmark == mark) mk.splice(i--, 1); + } + } + } + return taken; + }, + append:function (line) { + var mylen = this.text.length, mk = line.marked, mymk = this.marked; + this.text += line.text; + copyStyles(0, line.text.length, line.styles, this.styles); + if (mymk) { + for (var i = 0; i < mymk.length; ++i) + if (mymk[i].to == null) mymk[i].to = mylen; + } + if (mk && mk.length) { + if (!mymk) this.marked = mymk = []; + outer: for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + if (!mark.from) { + for (var j = 0; j < mymk.length; ++j) { + var mymark = mymk[j]; + if (mymark.to == mylen && mymark.sameSet(mark)) { + mymark.to = mark.to == null ? null : mark.to + mylen; + if (mymark.isDead()) { + mymark.detach(this); + mk.splice(i--, 1); + } + continue outer; + } + } + } + mymk.push(mark); + mark.attach(this); + mark.from += mylen; + if (mark.to != null) mark.to += mylen; + } + } + }, + fixMarkEnds:function (other) { + var mk = this.marked, omk = other.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i], close = mark.to == null; + if (close && omk) { + for (var j = 0; j < omk.length; ++j) + if (omk[j].sameSet(mark)) { + close = false; + break; + } + } + if (close) mark.to = this.text.length; + } + }, + fixMarkStarts:function () { + var mk = this.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) + if (mk[i].from == null) mk[i].from = 0; + }, + addMark:function (mark) { + mark.attach(this); + if (this.marked == null) this.marked = []; + this.marked.push(mark); + this.marked.sort(function (a, b) { + return (a.from || 0) - (b.from || 0); + }); + }, + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + highlight:function (mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; + var changed = false, curWord = st[0], prevWord; + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state); + var substr = this.text.slice(stream.start, stream.pos); + stream.start = stream.pos; + if (pos && st[pos - 1] == style) + st[pos - 2] += substr; + else if (substr) { + if (!changed && (st[pos + 1] != style || (pos && st[pos - 2] != prevWord))) changed = true; + st[pos++] = substr; + st[pos++] = style; + prevWord = curWord; + curWord = st[pos]; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); + st[pos++] = null; + break; + } + } + if (st.length != pos) { + st.length = pos; + changed = true; + } + if (pos && st[pos - 2] != prevWord) changed = true; + // Short lines with simple highlights return null, and are + // counted as changed by the driver because they are likely to + // highlight the same way in various contexts. + return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt:function (mode, state, ch) { + var txt = this.text, stream = new StringStream(txt); + while (stream.pos < ch && !stream.eol()) { + stream.start = stream.pos; + var style = mode.token(stream, state); + } + return {start:stream.start, + end:stream.pos, + string:stream.current(), + className:style || null, + state:state}; + }, + indentation:function (tabSize) { + return countColumn(this.text, null, tabSize); + }, + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + getHTML:function (makeTab, wrapAt, wrapId, wrapWBR) { + var html = [], first = true, col = 0; + + function span_(text, style) { + if (!text) return; + // Work around a bug where, in some compat modes, IE ignores leading spaces + if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); + first = false; + if (text.indexOf("\t") == -1) { + col += text.length; + var escaped = htmlEscape(text); + } else { + var escaped = ""; + for (var pos = 0; ;) { + var idx = text.indexOf("\t", pos); + if (idx == -1) { + escaped += htmlEscape(text.slice(pos)); + col += text.length - pos; + break; + } else { + col += idx - pos; + var tab = makeTab(col); + escaped += htmlEscape(text.slice(pos, idx)) + tab.html; + col += tab.width; + pos = idx + 1; + } + } + } + if (style) html.push('', escaped, ""); + else html.push(escaped); + } + + var span = span_; + if (wrapAt != null) { + var outPos = 0, open = ""; + span = function (text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + if (wrapAt > outPos) { + span_(text.slice(0, wrapAt - outPos), style); + // See comment at the definition of spanAffectsWrapping + if (wrapWBR) html.push(""); + } + html.push(open); + span_(text.slice(wrapAt - outPos), style); + html.push(""); + wrapAt--; + outPos += l; + } else { + outPos += l; + span_(text, style); + // Output empty wrapper when at end of line + if (outPos == wrapAt && outPos == len) html.push(open + ""); + // Stop outputting HTML when gone sufficiently far beyond measure + else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function () { + }; + } + } + } + + var st = this.styles, allText = this.text, marked = this.marked; + var len = allText.length; + + function styleToClass(style) { + if (!style) return null; + return "cm-" + style.replace(/ +/g, " cm-"); + } + + if (!allText && wrapAt == null) { + span(" "); + } else if (!marked || !marked.length) { + for (var i = 0, ch = 0; ch < len; i += 2) { + var str = st[i], style = st[i + 1], l = str.length; + if (ch + l > len) str = str.slice(0, len - ch); + ch += l; + span(str, styleToClass(style)); + } + } else { + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + + function advanceMarks() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.style != null) marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to || Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + } + + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; + span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) { + text = text.slice(upto - pos); + pos = upto; + break; + } + pos = end; + } + text = st[i++]; + style = styleToClass(st[i++]); + } + } + } + return html.join(""); + }, + cleanUp:function () { + this.parent = null; + if (this.marked) + for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + } + }; + // Utility used by replace and split above + function copyStyles(from, to, source, dest) { + for (var i = 0, pos = 0, state = 0; pos < to; i += 2) { + var part = source[i], end = pos + part.length; + if (state == 0) { + if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i + 1]); + if (end >= from) state = 1; + } + else if (state == 1) { + if (end > to) dest.push(part.slice(0, to - pos), source[i + 1]); + else dest.push(part, source[i + 1]); + } + pos = end; + } + } + + // Data structure that holds the sequence of lines. + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, e = lines.length, height = 0; i < e; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize:function () { + return this.lines.length; + }, + remove:function (at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse:function (lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertHeight:function (at, lines, height) { + this.height += height; + // The trick below is apparently too advanced for IE, which + // occasionally corrupts this.lines (duplicating elements) when + // it is used. + if (ie) this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + else this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; + }, + iterN:function (at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0, e = children.length; i < e; ++i) { + var ch = children[i]; + size += ch.chunkSize(); + height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize:function () { + return this.size; + }, + remove:function (at, n, callbacks) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.remove(at, rm, callbacks); + this.height -= oldHeight - child.height; + if (sz == rm) { + this.children.splice(i--, 1); + child.parent = null; + } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + if (this.size - n < 25) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse:function (lines) { + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); + }, + insert:function (at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight:function (at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertHeight(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + maybeSpill:function () { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iter:function (from, to, op) { + this.iterN(from, to - from, op); + }, + iterN:function (at, n, op) { + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + function getLineAt(chunk, n) { + while (!chunk.lines) { + for (var i = 0; ; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { + chunk = child; + break; + } + n -= sz; + } + } + return chunk.lines[n]; + } + + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + + function lineAtHeight(chunk, h) { + var n = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { + chunk = child; + continue outer; + } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0, e = chunk.lines.length; i < e; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + function heightAtLine(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { + chunk = child; + continue outer; + } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + + // The history object 'chunks' changes that are made close together + // and at almost the same time into bigger undoable units. + function History() { + this.time = 0; + this.done = []; + this.undone = []; + this.compound = 0; + this.closed = false; + } + + History.prototype = { + addChange:function (start, added, old) { + this.undone.length = 0; + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + + if (this.compound && cur && !this.closed) { + cur.push({start:start, added:added, old:old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([ + {start:start, added:added, old:old} + ]); + this.closed = false; + } else { + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); + } + this.time = time; + }, + startCompound:function () { + if (!this.compound++) this.closed = true; + }, + endCompound:function () { + if (!--this.compound) this.closed = true; + } + }; + + function stopMethod() { + e_stop(this); + } + + // Ensure an event has a stop method. + function addStop(event) { + if (!event.stop) event.stop = stopMethod; + return event; + } + + function e_preventDefault(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + + function e_stopPropagation(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + } + + function e_stop(e) { + e_preventDefault(e); + e_stopPropagation(e); + } + + CodeMirror.e_stop = e_stop; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + + function e_target(e) { + return e.target || e.srcElement; + } + + function e_button(e) { + if (e.which) return e.which; + else if (e.button & 1) return 1; + else if (e.button & 2) return 3; + else if (e.button & 4) return 2; + } + + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + + // Event handler registration. If disconnect is true, it'll return a + // function that unregisters the handler. + function connect(node, type, handler, disconnect) { + if (typeof node.addEventListener == "function") { + node.addEventListener(type, handler, false); + if (disconnect) return function () { + node.removeEventListener(type, handler, false); + }; + } + else { + var wrapHandler = function (event) { + handler(event || window.event); + }; + node.attachEvent("on" + type, wrapHandler); + if (disconnect) return function () { + node.detachEvent("on" + type, wrapHandler); + }; + } + } + + CodeMirror.connect = connect; + + function Delayed() { + this.id = null; + } + + Delayed.prototype = {set:function (ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }}; + + var Pass = CodeMirror.Pass = {toString:function () { + return "CodeMirror.Pass"; + }}; + + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; + var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + + // Detect drag-and-drop + var dragAndDrop = function () { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = document.createElement('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + // Feature-detect whether newlines in textareas are converted to \r\n + var lineSep = function () { + var te = document.createElement("textarea"); + te.value = "foo\nbar"; + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + + function computedStyle(elt) { + if (elt.currentStyle) return elt.currentStyle; + return window.getComputedStyle(elt, null); + } + + // Find the position of an element by following the offsetParent chain. + // If screen==true, it returns screen (rather than page) coordinates. + function eltOffset(node, screen) { + var bod = node.ownerDocument.body; + var x = 0, y = 0, skipBody = false; + for (var n = node; n; n = n.offsetParent) { + var ol = n.offsetLeft, ot = n.offsetTop; + // Firefox reports weird inverted offsets when the body has a border. + if (n == bod) { + x += Math.abs(ol); + y += Math.abs(ot); + } + else { + x += ol, y += ot; + } + if (screen && computedStyle(n).position == "fixed") + skipBody = true; + } + var e = screen && !skipBody ? null : bod; + for (var n = node.parentNode; n != e; n = n.parentNode) + if (n.scrollLeft != null) { + x -= n.scrollLeft; + y -= n.scrollTop; + } + return {left:x, top:y}; + } + + // Use the faster and saner getBoundingClientRect method when possible. + if (document.documentElement.getBoundingClientRect != null) eltOffset = function (node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { + var box = node.getBoundingClientRect(); + box = { top:box.top, left:box.left }; + } + catch (e) { + box = {top:0, left:0}; + } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; + box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; + box.left += window.pageXOffset; + } + } + return box; + }; + + // Get a node's text content. + function eltText(node) { + return node.textContent || node.innerText || node.nodeValue || ""; + } + + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } + + // Operations on {line, ch} objects. + function posEq(a, b) { + return a.line == b.line && a.ch == b.ch; + } + + function posLess(a, b) { + return a.line < b.line || (a.line == b.line && a.ch < b.ch); + } + + function copyPos(x) { + return {line:x.line, ch:x.ch}; + } + + var escapeElement = document.createElement("pre"); + + function htmlEscape(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML; + } + + // Recent (late 2011) Opera betas insert bogus newlines at the start + // of the textContent, so we strip those. + if (htmlEscape("a") == "\na") + htmlEscape = function (str) { + escapeElement.textContent = str; + return escapeElement.innerHTML.slice(1); + }; + // Some IEs don't preserve tabs through innerHTML + else if (htmlEscape("\t") != "\t") + htmlEscape = function (str) { + escapeElement.innerHTML = ""; + escapeElement.appendChild(document.createTextNode(str)); + return escapeElement.innerHTML; + }; + CodeMirror.htmlEscape = htmlEscape; + + // Used to position the cursor after an undo/redo by finding the + // last edited character. + function editEnd(from, to) { + if (!to) return 0; + if (!from) return to.length; + for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) + if (from.charAt(i) != to.charAt(j)) break; + return j + 1; + } + + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + + function isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, nl, result = []; + while ((nl = string.indexOf("\n", pos)) > -1) { + result.push(string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl)); + pos = nl + 1; + } + result.push(string.slice(pos)); + return result; + } : function (string) { + return string.split(/\r?\n/); + }; + CodeMirror.splitLines = splitLines; + + var hasSelection = window.getSelection ? function (te) { + try { + return te.selectionStart != te.selectionEnd; + } + catch (e) { + return false; + } + } : function (te) { + try { + var range = te.ownerDocument.selection.createRange(); + } + catch (e) { + } + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + CodeMirror.defineMode("null", function () { + return {token:function (stream) { + stream.skipToEnd(); + }}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + var keyNames = {3:"Enter", 8:"Backspace", 9:"Tab", 13:"Enter", 16:"Shift", 17:"Ctrl", 18:"Alt", + 19:"Pause", 20:"CapsLock", 27:"Esc", 32:"Space", 33:"PageUp", 34:"PageDown", 35:"End", + 36:"Home", 37:"Left", 38:"Up", 39:"Right", 40:"Down", 44:"PrintScrn", 45:"Insert", + 46:"Delete", 59:";", 91:"Mod", 92:"Mod", 93:"Mod", 127:"Delete", 186:";", 187:"=", 188:",", + 189:"-", 190:".", 191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"'", 63276:"PageUp", + 63277:"PageDown", 63275:"End", 63273:"Home", 63234:"Left", 63232:"Up", 63235:"Right", + 63233:"Down", 63302:"Insert", 63272:"Delete"}; + CodeMirror.keyNames = keyNames; + (function () { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + return CodeMirror; +})(); diff --git a/templates/assets/javascripts/jquery-1-7-2.js b/templates/assets/javascripts/jquery-1-7-2.js new file mode 100644 index 00000000..e2c69156 --- /dev/null +++ b/templates/assets/javascripts/jquery-1-7-2.js @@ -0,0 +1,9401 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Wed Mar 21 12:46:34 2012 -0700 + */ +(function (window, undefined) { + +// Use the correct document accordingly with window argument (sandbox) + var document = window.document, + navigator = window.navigator, + location = window.location; + var jQuery = (function () { + +// Define a local copy of jQuery + var jQuery = function (selector, context) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init(selector, context, rootjQuery); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function (all, letter) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + + jQuery.fn = jQuery.prototype = { + constructor:jQuery, + init:function (selector, context, rootjQuery) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if (!selector) { + return this; + } + + // Handle $(DOMElement) + if (selector.nodeType) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if (selector === "body" && !context && document.body) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if (typeof selector === "string") { + // Are we dealing with HTML string or an ID? + if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec(selector); + } + + // Verify a match, and that no context was specified for #id + if (match && (match[1] || !context)) { + + // HANDLE: $(html) -> $(array) + if (match[1]) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec(selector); + + if (ret) { + if (jQuery.isPlainObject(context)) { + selector = [ document.createElement(ret[1]) ]; + jQuery.fn.attr.call(selector, context, true); + + } else { + selector = [ doc.createElement(ret[1]) ]; + } + + } else { + ret = jQuery.buildFragment([ match[1] ], [ doc ]); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge(this, selector); + + // HANDLE: $("#id") + } else { + elem = document.getElementById(match[2]); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE and Opera return items + // by name instead of ID + if (elem.id !== match[2]) { + return rootjQuery.find(selector); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if (!context || context.jquery) { + return ( context || rootjQuery ).find(selector); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor(context).find(selector); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if (jQuery.isFunction(selector)) { + return rootjQuery.ready(selector); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray(selector, this); + }, + + // Start with an empty selector + selector:"", + + // The current version of jQuery being used + jquery:"1.7.2", + + // The default length of a jQuery object is 0 + length:0, + + // The number of elements contained in the matched element set + size:function () { + return this.length; + }, + + toArray:function () { + return slice.call(this, 0); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get:function (num) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack:function (elems, name, selector) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if (jQuery.isArray(elems)) { + push.apply(ret, elems); + + } else { + jQuery.merge(ret, elems); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if (name === "find") { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if (name) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each:function (callback, args) { + return jQuery.each(this, callback, args); + }, + + ready:function (fn) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add(fn); + + return this; + }, + + eq:function (i) { + i = +i; + return i === -1 ? + this.slice(i) : + this.slice(i, i + 1); + }, + + first:function () { + return this.eq(0); + }, + + last:function () { + return this.eq(-1); + }, + + slice:function () { + return this.pushStack(slice.apply(this, arguments), + "slice", slice.call(arguments).join(",")); + }, + + map:function (callback) { + return this.pushStack(jQuery.map(this, function (elem, i) { + return callback.call(elem, i, elem); + })); + }, + + end:function () { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push:push, + sort:[].sort, + splice:[].splice + }; + +// Give the init function the jQuery prototype for later instantiation + jQuery.fn.init.prototype = jQuery.fn; + + jQuery.extend = jQuery.fn.extend = function () { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && !jQuery.isFunction(target)) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if (length === i) { + target = this; + --i; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[ i ]) != null) { + // Extend the base object + for (name in options) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )) { + if (copyIsArray) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + jQuery.extend({ + noConflict:function (deep) { + if (window.$ === jQuery) { + window.$ = _$; + } + + if (deep && window.jQuery === jQuery) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady:false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait:1, + + // Hold (or release) the ready event + holdReady:function (hold) { + if (hold) { + jQuery.readyWait++; + } else { + jQuery.ready(true); + } + }, + + // Handle when the DOM is ready + ready:function (wait) { + // Either a released hold or an DOMready/load event and not yet ready + if ((wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady)) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if (!document.body) { + return setTimeout(jQuery.ready, 1); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if (wait !== true && --jQuery.readyWait > 0) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith(document, [ jQuery ]); + + // Trigger any bound ready events + if (jQuery.fn.trigger) { + jQuery(document).trigger("ready").off("ready"); + } + } + }, + + bindReady:function () { + if (readyList) { + return; + } + + readyList = jQuery.Callbacks("once memory"); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if (document.readyState === "complete") { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout(jQuery.ready, 1); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if (document.addEventListener) { + // Use the handy event callback + document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); + + // A fallback to window.onload, that will always work + window.addEventListener("load", jQuery.ready, false); + + // If IE event model is used + } else if (document.attachEvent) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent("onload", jQuery.ready); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch (e) { + } + + if (document.documentElement.doScroll && toplevel) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction:function (obj) { + return jQuery.type(obj) === "function"; + }, + + isArray:Array.isArray || function (obj) { + return jQuery.type(obj) === "array"; + }, + + isWindow:function (obj) { + return obj != null && obj == obj.window; + }, + + isNumeric:function (obj) { + return !isNaN(parseFloat(obj)) && isFinite(obj); + }, + + type:function (obj) { + return obj == null ? + String(obj) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject:function (obj) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if (!obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { + return false; + } + + try { + // Not own constructor property must be Object + if (obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { + return false; + } + } catch (e) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for (key in obj) { + } + + return key === undefined || hasOwn.call(obj, key); + }, + + isEmptyObject:function (obj) { + for (var name in obj) { + return false; + } + return true; + }, + + error:function (msg) { + throw new Error(msg); + }, + + parseJSON:function (data) { + if (typeof data !== "string" || !data) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim(data); + + // Attempt to parse using the native JSON parser first + if (window.JSON && window.JSON.parse) { + return window.JSON.parse(data); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if (rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, ""))) { + + return ( new Function("return " + data) )(); + + } + jQuery.error("Invalid JSON: " + data); + }, + + // Cross-browser xml parsing + parseXML:function (data) { + if (typeof data !== "string" || !data) { + return null; + } + var xml, tmp; + try { + if (window.DOMParser) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString(data, "text/xml"); + } else { // IE + xml = new ActiveXObject("Microsoft.XMLDOM"); + xml.async = "false"; + xml.loadXML(data); + } + } catch (e) { + xml = undefined; + } + if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length) { + jQuery.error("Invalid XML: " + data); + } + return xml; + }, + + noop:function () { + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval:function (data) { + if (data && rnotwhite.test(data)) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function (data) { + window[ "eval" ].call(window, data); + } )(data); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase:function (string) { + return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); + }, + + nodeName:function (elem, name) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each:function (object, callback, args) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if (args) { + if (isObj) { + for (name in object) { + if (callback.apply(object[ name ], args) === false) { + break; + } + } + } else { + for (; i < length;) { + if (callback.apply(object[ i++ ], args) === false) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if (isObj) { + for (name in object) { + if (callback.call(object[ name ], name, object[ name ]) === false) { + break; + } + } + } else { + for (; i < length;) { + if (callback.call(object[ i ], i, object[ i++ ]) === false) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim:trim ? + function (text) { + return text == null ? + "" : + trim.call(text); + } : + + // Otherwise use our own trimming functionality + function (text) { + return text == null ? + "" : + text.toString().replace(trimLeft, "").replace(trimRight, ""); + }, + + // results is for internal usage only + makeArray:function (array, results) { + var ret = results || []; + + if (array != null) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if (array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow(array)) { + push.call(ret, array); + } else { + jQuery.merge(ret, array); + } + } + + return ret; + }, + + inArray:function (elem, array, i) { + var len; + + if (array) { + if (indexOf) { + return indexOf.call(array, elem, i); + } + + len = array.length; + i = i ? i < 0 ? Math.max(0, len + i) : i : 0; + + for (; i < len; i++) { + // Skip accessing in sparse arrays + if (i in array && array[ i ] === elem) { + return i; + } + } + } + + return -1; + }, + + merge:function (first, second) { + var i = first.length, + j = 0; + + if (typeof second.length === "number") { + for (var l = second.length; j < l; j++) { + first[ i++ ] = second[ j ]; + } + + } else { + while (second[j] !== undefined) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep:function (elems, callback, inv) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for (var i = 0, length = elems.length; i < length; i++) { + retVal = !!callback(elems[ i ], i); + if (inv !== retVal) { + ret.push(elems[ i ]); + } + } + + return ret; + }, + + // arg is for internal usage only + map:function (elems, callback, arg) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length - 1 ] ) || length === 0 || jQuery.isArray(elems) ); + + // Go through the array, translating each of the items to their + if (isArray) { + for (; i < length; i++) { + value = callback(elems[ i ], i, arg); + + if (value != null) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for (key in elems) { + value = callback(elems[ key ], key, arg); + + if (value != null) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply([], ret); + }, + + // A global GUID counter for objects + guid:1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy:function (fn, context) { + if (typeof context === "string") { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if (!jQuery.isFunction(fn)) { + return undefined; + } + + // Simulated bind + var args = slice.call(arguments, 2), + proxy = function () { + return fn.apply(context, args.concat(slice.call(arguments))); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access:function (elems, fn, key, value, chainable, emptyGet, pass) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if (key && typeof key === "object") { + for (i in key) { + jQuery.access(elems, fn, i, key[i], 1, emptyGet, value); + } + chainable = 1; + + // Sets one value + } else if (value !== undefined) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction(value); + + if (bulk) { + // Bulk operations only iterate when executing function values + if (exec) { + exec = fn; + fn = function (elem, key, value) { + return exec.call(jQuery(elem), value); + }; + + // Otherwise they run against the entire set + } else { + fn.call(elems, value); + fn = null; + } + } + + if (fn) { + for (; i < length; i++) { + fn(elems[i], key, exec ? value.call(elems[i], i, fn(elems[i], key)) : value, pass); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call(elems) : + length ? fn(elems[0], key) : emptyGet; + }, + + now:function () { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch:function (ua) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec(ua) || + ropera.exec(ua) || + rmsie.exec(ua) || + ua.indexOf("compatible") < 0 && rmozilla.exec(ua) || + []; + + return { browser:match[1] || "", version:match[2] || "0" }; + }, + + sub:function () { + function jQuerySub(selector, context) { + return new jQuerySub.fn.init(selector, context); + } + + jQuery.extend(true, jQuerySub, this); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init(selector, context) { + if (context && context instanceof jQuery && !(context instanceof jQuerySub)) { + context = jQuerySub(context); + } + + return jQuery.fn.init.call(this, selector, context, rootjQuerySub); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser:{} + }); + +// Populate the class2type map + jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function (i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + }); + + browserMatch = jQuery.uaMatch(userAgent); + if (browserMatch.browser) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; + } + +// Deprecated, use jQuery.browser.webkit instead + if (jQuery.browser.webkit) { + jQuery.browser.safari = true; + } + +// IE doesn't match non-breaking spaces with \s + if (rnotwhite.test("\xA0")) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; + } + +// All jQuery objects should point back to these + rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method + if (document.addEventListener) { + DOMContentLoaded = function () { + document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); + jQuery.ready(); + }; + + } else if (document.attachEvent) { + DOMContentLoaded = function () { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", DOMContentLoaded); + jQuery.ready(); + } + }; + } + +// The DOM ready check for Internet Explorer + function doScrollCheck() { + if (jQuery.isReady) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch (e) { + setTimeout(doScrollCheck, 1); + return; + } + + // and execute any waiting functions + jQuery.ready(); + } + + return jQuery; + + })(); + + +// String to Object flags format cache + var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache + function createFlags(flags) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split(/\s+/); + for (i = 0, length = flags.length; i < length; i++) { + object[ flags[i] ] = true; + } + return object; + } + + /* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ + jQuery.Callbacks = function (flags) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags(flags) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function (args) { + var i, + length, + elem, + type, + actual; + for (i = 0, length = args.length; i < length; i++) { + elem = args[ i ]; + type = jQuery.type(elem); + if (type === "array") { + // Inspect recursively + add(elem); + } else if (type === "function") { + // Add if not in unique mode and callback is not in + if (!flags.unique || !self.has(elem)) { + list.push(elem); + } + } + } + }, + // Fire callbacks + fire = function (context, args) { + args = args || []; + memory = !flags.memory || [ context, args ]; + fired = true; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for (; list && firingIndex < firingLength; firingIndex++) { + if (list[ firingIndex ].apply(context, args) === false && flags.stopOnFalse) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if (list) { + if (!flags.once) { + if (stack && stack.length) { + memory = stack.shift(); + self.fireWith(memory[ 0 ], memory[ 1 ]); + } + } else if (memory === true) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add:function () { + if (list) { + var length = list.length; + add(arguments); + // Do we need to add the callbacks to the + // current firing batch? + if (firing) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if (memory && memory !== true) { + firingStart = length; + fire(memory[ 0 ], memory[ 1 ]); + } + } + return this; + }, + // Remove a callback from the list + remove:function () { + if (list) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for (; argIndex < argLength; argIndex++) { + for (var i = 0; i < list.length; i++) { + if (args[ argIndex ] === list[ i ]) { + // Handle firingIndex and firingLength + if (firing) { + if (i <= firingLength) { + firingLength--; + if (i <= firingIndex) { + firingIndex--; + } + } + } + // Remove the element + list.splice(i--, 1); + // If we have some unicity property then + // we only need to do this once + if (flags.unique) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has:function (fn) { + if (list) { + var i = 0, + length = list.length; + for (; i < length; i++) { + if (fn === list[ i ]) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty:function () { + list = []; + return this; + }, + // Have the list do nothing anymore + disable:function () { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled:function () { + return !list; + }, + // Lock the list in its current state + lock:function () { + stack = undefined; + if (!memory || memory === true) { + self.disable(); + } + return this; + }, + // Is it locked? + locked:function () { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith:function (context, args) { + if (stack) { + if (firing) { + if (!flags.once) { + stack.push([ context, args ]); + } + } else if (!( flags.once && memory )) { + fire(context, args); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire:function () { + self.fireWith(this, arguments); + return this; + }, + // To know if the callbacks have already been called at least once + fired:function () { + return !!fired; + } + }; + + return self; + }; + + + var // Static reference to slice + sliceDeferred = [].slice; + + jQuery.extend({ + + Deferred:function (func) { + var doneList = jQuery.Callbacks("once memory"), + failList = jQuery.Callbacks("once memory"), + progressList = jQuery.Callbacks("memory"), + state = "pending", + lists = { + resolve:doneList, + reject:failList, + notify:progressList + }, + promise = { + done:doneList.add, + fail:failList.add, + progress:progressList.add, + + state:function () { + return state; + }, + + // Deprecated + isResolved:doneList.fired, + isRejected:failList.fired, + + then:function (doneCallbacks, failCallbacks, progressCallbacks) { + deferred.done(doneCallbacks).fail(failCallbacks).progress(progressCallbacks); + return this; + }, + always:function () { + deferred.done.apply(deferred, arguments).fail.apply(deferred, arguments); + return this; + }, + pipe:function (fnDone, fnFail, fnProgress) { + return jQuery.Deferred( + function (newDefer) { + jQuery.each({ + done:[ fnDone, "resolve" ], + fail:[ fnFail, "reject" ], + progress:[ fnProgress, "notify" ] + }, function (handler, data) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if (jQuery.isFunction(fn)) { + deferred[ handler ](function () { + returned = fn.apply(this, arguments); + if (returned && jQuery.isFunction(returned.promise)) { + returned.promise().then(newDefer.resolve, newDefer.reject, newDefer.notify); + } else { + newDefer[ action + "With" ](this === deferred ? newDefer : this, [ returned ]); + } + }); + } else { + deferred[ handler ](newDefer[ action ]); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise:function (obj) { + if (obj == null) { + obj = promise; + } else { + for (var key in promise) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for (key in lists) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( + function () { + state = "resolved"; + }, failList.disable, progressList.lock).fail(function () { + state = "rejected"; + }, doneList.disable, progressList.lock); + + // Call given func if any + if (func) { + func.call(deferred, deferred); + } + + // All done! + return deferred; + }, + + // Deferred helper + when:function (firstParam) { + var args = sliceDeferred.call(arguments, 0), + i = 0, + length = args.length, + pValues = new Array(length), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction(firstParam.promise) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + + function resolveFunc(i) { + return function (value) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value; + if (!( --count )) { + deferred.resolveWith(deferred, args); + } + }; + } + + function progressFunc(i) { + return function (value) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value; + deferred.notifyWith(promise, pValues); + }; + } + + if (length > 1) { + for (; i < length; i++) { + if (args[ i ] && args[ i ].promise && jQuery.isFunction(args[ i ].promise)) { + args[ i ].promise().then(resolveFunc(i), deferred.reject, progressFunc(i)); + } else { + --count; + } + } + if (!count) { + deferred.resolveWith(deferred, args); + } + } else if (deferred !== firstParam) { + deferred.resolveWith(deferred, length ? [ firstParam ] : []); + } + return promise; + } + }); + + + jQuery.support = (function () { + + var support, + all, + a, + select, + opt, + input, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement("div"), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + + // Can't get basic test support + if (!all || !all.length || !a) { + return {}; + } + + // First batch of supports tests + select = document.createElement("select"); + opt = select.appendChild(document.createElement("option")); + input = div.getElementsByTagName("input")[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace:( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody:!div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize:!!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style:/top/.test(a.getAttribute("style")), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized:( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity:/^0.55/.test(a.style.opacity), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat:!!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn:( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected:opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute:div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype:!!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone:document.createElement("nav").cloneNode(true).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles:true, + changeBubbles:true, + focusinBubbles:false, + deleteExpando:true, + noCloneEvent:true, + inlineBlockNeedsLayout:false, + shrinkWrapBlocks:false, + reliableMarginRight:true, + pixelMargin:true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode(true).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch (e) { + support.deleteExpando = false; + } + + if (!div.addEventListener && div.attachEvent && div.fireEvent) { + div.attachEvent("onclick", function () { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode(true).fireEvent("onclick"); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute("name", "t"); + + div.appendChild(input); + fragment = document.createDocumentFragment(); + fragment.appendChild(div.lastChild); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild(input); + fragment.appendChild(div); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if (div.attachEvent) { + for (i in { + submit:1, + change:1, + focusin:1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if (!isSupported) { + div.setAttribute(eventName, "return;"); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild(div); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function () { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if (!body) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore(container, body.firstChild); + + // Construct the test element + div = document.createElement("div"); + container.appendChild(div); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if (window.getComputedStyle) { + div.innerHTML = ""; + marginDiv = document.createElement("div"); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild(marginDiv); + support.reliableMarginRight = + ( parseInt(( window.getComputedStyle(marginDiv, null) || { marginRight:0 } ).marginRight, 10) || 0 ) === 0; + } + + if (typeof div.style.zoom !== "undefined") { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder:( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells:( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if (window.getComputedStyle) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle(div, null) || { marginTop:0 } ).marginTop !== "1%"; + } + + if (typeof container.style.zoom !== "undefined") { + container.style.zoom = 1; + } + + body.removeChild(container); + marginDiv = div = container = null; + + jQuery.extend(support, offsetSupport); + }); + + return support; + })(); + + + var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + + jQuery.extend({ + cache:{}, + + // Please use with caution + uuid:0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando:"jQuery" + ( jQuery.fn.jquery + Math.random() ).replace(/\D/g, ""), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData:{ + "embed":true, + // Ban all objects except for Flash (which handle expandos) + "object":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet":true + }, + + hasData:function (elem) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject(elem); + }, + + data:function (elem, name, data, pvt /* Internal Use Only */) { + if (!jQuery.acceptData(elem)) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ((!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined) { + return; + } + + if (!id) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if (isNode) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if (!cache[ id ]) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if (!isNode) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if (typeof name === "object" || typeof name === "function") { + if (pvt) { + cache[ id ] = jQuery.extend(cache[ id ], name); + } else { + cache[ id ].data = jQuery.extend(cache[ id ].data, name); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if (!pvt) { + if (!thisCache.data) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if (data !== undefined) { + thisCache[ jQuery.camelCase(name) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if (isEvents && !thisCache[ name ]) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if (getByName) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if (ret == null) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase(name) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData:function (elem, name, pvt /* Internal Use Only */) { + if (!jQuery.acceptData(elem)) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if (!cache[ id ]) { + return; + } + + if (name) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if (thisCache) { + + // Support array or space separated string names for data keys + if (!jQuery.isArray(name)) { + + // try the string as a key before any manipulation + if (name in thisCache) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase(name); + if (name in thisCache) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } + + for (i = 0, l = name.length; i < l; i++) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if (!( pvt ? isEmptyDataObject : jQuery.isEmptyObject )(thisCache)) { + return; + } + } + } + + // See jQuery.data for more information + if (!pvt) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if (!isEmptyDataObject(cache[ id ])) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if (jQuery.support.deleteExpando || !cache.setInterval) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if (isNode) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if (jQuery.support.deleteExpando) { + delete elem[ internalKey ]; + } else if (elem.removeAttribute) { + elem.removeAttribute(internalKey); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data:function (elem, name, data) { + return jQuery.data(elem, name, data, true); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData:function (elem) { + if (elem.nodeName) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if (match) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } + }); + + jQuery.fn.extend({ + data:function (key, value) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if (key === undefined) { + if (this.length) { + data = jQuery.data(elem); + + if (elem.nodeType === 1 && !jQuery._data(elem, "parsedAttrs")) { + attr = elem.attributes; + for (l = attr.length; i < l; i++) { + name = attr[i].name; + + if (name.indexOf("data-") === 0) { + name = jQuery.camelCase(name.substring(5)); + + dataAttr(elem, name, data[ name ]); + } + } + jQuery._data(elem, "parsedAttrs", true); + } + } + + return data; + } + + // Sets multiple values + if (typeof key === "object") { + return this.each(function () { + jQuery.data(this, key); + }); + } + + parts = key.split(".", 2); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access(this, function (value) { + + if (value === undefined) { + data = this.triggerHandler("getData" + part, [ parts[0] ]); + + // Try to fetch any internally stored data first + if (data === undefined && elem) { + data = jQuery.data(elem, key); + data = dataAttr(elem, key, data); + } + + return data === undefined && parts[1] ? + this.data(parts[0]) : + data; + } + + parts[1] = value; + this.each(function () { + var self = jQuery(this); + + self.triggerHandler("setData" + part, parts); + jQuery.data(this, key, value); + self.triggerHandler("changeData" + part, parts); + }); + }, null, value, arguments.length > 1, null, false); + }, + + removeData:function (key) { + return this.each(function () { + jQuery.removeData(this, key); + }); + } + }); + + function dataAttr(elem, key, data) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if (data === undefined && elem.nodeType === 1) { + + var name = "data-" + key.replace(rmultiDash, "-$1").toLowerCase(); + + data = elem.getAttribute(name); + + if (typeof data === "string") { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric(data) ? +data : + rbrace.test(data) ? jQuery.parseJSON(data) : + data; + } catch (e) { + } + + // Make sure we set the data so it isn't changed later + jQuery.data(elem, key, data); + + } else { + data = undefined; + } + } + + return data; + } + +// checks a cache object for emptiness + function isEmptyDataObject(obj) { + for (var name in obj) { + + // if the public data object is empty, the private is still empty + if (name === "data" && jQuery.isEmptyObject(obj[name])) { + continue; + } + if (name !== "toJSON") { + return false; + } + } + + return true; + } + + + function handleQueueMarkDefer(elem, type, src) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data(elem, deferDataKey); + if (defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) )) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout(function () { + if (!jQuery._data(elem, queueDataKey) && + !jQuery._data(elem, markDataKey)) { + jQuery.removeData(elem, deferDataKey, true); + defer.fire(); + } + }, 0); + } + } + + jQuery.extend({ + + _mark:function (elem, type) { + if (elem) { + type = ( type || "fx" ) + "mark"; + jQuery._data(elem, type, (jQuery._data(elem, type) || 0) + 1); + } + }, + + _unmark:function (force, elem, type) { + if (force !== true) { + type = elem; + elem = force; + force = false; + } + if (elem) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data(elem, key) || 1) - 1 ); + if (count) { + jQuery._data(elem, key, count); + } else { + jQuery.removeData(elem, key, true); + handleQueueMarkDefer(elem, type, "mark"); + } + } + }, + + queue:function (elem, type, data) { + var q; + if (elem) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data(elem, type); + + // Speed up dequeue by getting out quickly if this is just a lookup + if (data) { + if (!q || jQuery.isArray(data)) { + q = jQuery._data(elem, type, jQuery.makeArray(data)); + } else { + q.push(data); + } + } + return q || []; + } + }, + + dequeue:function (elem, type) { + type = type || "fx"; + + var queue = jQuery.queue(elem, type), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if (fn === "inprogress") { + fn = queue.shift(); + } + + if (fn) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if (type === "fx") { + queue.unshift("inprogress"); + } + + jQuery._data(elem, type + ".run", hooks); + fn.call(elem, function () { + jQuery.dequeue(elem, type); + }, hooks); + } + + if (!queue.length) { + jQuery.removeData(elem, type + "queue " + type + ".run", true); + handleQueueMarkDefer(elem, type, "queue"); + } + } + }); + + jQuery.fn.extend({ + queue:function (type, data) { + var setter = 2; + + if (typeof type !== "string") { + data = type; + type = "fx"; + setter--; + } + + if (arguments.length < setter) { + return jQuery.queue(this[0], type); + } + + return data === undefined ? + this : + this.each(function () { + var queue = jQuery.queue(this, type, data); + + if (type === "fx" && queue[0] !== "inprogress") { + jQuery.dequeue(this, type); + } + }); + }, + dequeue:function (type) { + return this.each(function () { + jQuery.dequeue(this, type); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay:function (time, type) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue(type, function (next, hooks) { + var timeout = setTimeout(next, time); + hooks.stop = function () { + clearTimeout(timeout); + }; + }); + }, + clearQueue:function (type) { + return this.queue(type || "fx", []); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise:function (type, object) { + if (typeof type !== "string") { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + + function resolve() { + if (!( --count )) { + defer.resolveWith(elements, [ elements ]); + } + } + + while (i--) { + if (( tmp = jQuery.data(elements[ i ], deferDataKey, undefined, true) || + ( jQuery.data(elements[ i ], queueDataKey, undefined, true) || + jQuery.data(elements[ i ], markDataKey, undefined, true) ) && + jQuery.data(elements[ i ], deferDataKey, jQuery.Callbacks("once memory"), true) )) { + count++; + tmp.add(resolve); + } + } + resolve(); + return defer.promise(object); + } + }); + + + var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + + jQuery.fn.extend({ + attr:function (name, value) { + return jQuery.access(this, jQuery.attr, name, value, arguments.length > 1); + }, + + removeAttr:function (name) { + return this.each(function () { + jQuery.removeAttr(this, name); + }); + }, + + prop:function (name, value) { + return jQuery.access(this, jQuery.prop, name, value, arguments.length > 1); + }, + + removeProp:function (name) { + name = jQuery.propFix[ name ] || name; + return this.each(function () { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch (e) { + } + }); + }, + + addClass:function (value) { + var classNames, i, l, elem, + setClass, c, cl; + + if (jQuery.isFunction(value)) { + return this.each(function (j) { + jQuery(this).addClass(value.call(this, j, this.className)); + }); + } + + if (value && typeof value === "string") { + classNames = value.split(rspace); + + for (i = 0, l = this.length; i < l; i++) { + elem = this[ i ]; + + if (elem.nodeType === 1) { + if (!elem.className && classNames.length === 1) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for (c = 0, cl = classNames.length; c < cl; c++) { + if (!~setClass.indexOf(" " + classNames[ c ] + " ")) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim(setClass); + } + } + } + } + + return this; + }, + + removeClass:function (value) { + var classNames, i, l, elem, className, c, cl; + + if (jQuery.isFunction(value)) { + return this.each(function (j) { + jQuery(this).removeClass(value.call(this, j, this.className)); + }); + } + + if ((value && typeof value === "string") || value === undefined) { + classNames = ( value || "" ).split(rspace); + + for (i = 0, l = this.length; i < l; i++) { + elem = this[ i ]; + + if (elem.nodeType === 1 && elem.className) { + if (value) { + className = (" " + elem.className + " ").replace(rclass, " "); + for (c = 0, cl = classNames.length; c < cl; c++) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim(className); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass:function (value, stateVal) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if (jQuery.isFunction(value)) { + return this.each(function (i) { + jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal); + }); + } + + return this.each(function () { + if (type === "string") { + // toggle individual class names + var className, + i = 0, + self = jQuery(this), + state = stateVal, + classNames = value.split(rspace); + + while ((className = classNames[ i++ ])) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass(className); + self[ state ? "addClass" : "removeClass" ](className); + } + + } else if (type === "undefined" || type === "boolean") { + if (this.className) { + // store className if set + jQuery._data(this, "__className__", this.className); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data(this, "__className__") || ""; + } + }); + }, + + hasClass:function (selector) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for (; i < l; i++) { + if (this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf(className) > -1) { + return true; + } + } + + return false; + }, + + val:function (value) { + var hooks, ret, isFunction, + elem = this[0]; + + if (!arguments.length) { + if (elem) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction(value); + + return this.each(function (i) { + var self = jQuery(this), val; + + if (this.nodeType !== 1) { + return; + } + + if (isFunction) { + val = value.call(this, i, self.val()); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if (val == null) { + val = ""; + } else if (typeof val === "number") { + val += ""; + } else if (jQuery.isArray(val)) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { + this.value = val; + } + }); + } + }); + + jQuery.extend({ + valHooks:{ + option:{ + get:function (elem) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select:{ + get:function (elem) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if (index < 0) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for (; i < max; i++) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if (option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup"))) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if (one) { + return value; + } + + // Multi-Selects return an array + values.push(value); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if (one && !values.length && options.length) { + return jQuery(options[ index ]).val(); + } + + return values; + }, + + set:function (elem, value) { + var values = jQuery.makeArray(value); + + jQuery(elem).find("option").each(function () { + this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0; + }); + + if (!values.length) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn:{ + val:true, + css:true, + html:true, + text:true, + data:true, + width:true, + height:true, + offset:true + }, + + attr:function (elem, name, value, pass) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return; + } + + if (pass && name in jQuery.attrFn) { + return jQuery(elem)[ name ](value); + } + + // Fallback to prop when attributes are not supported + if (typeof elem.getAttribute === "undefined") { + return jQuery.prop(elem, name, value); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if (notxml) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test(name) ? boolHook : nodeHook ); + } + + if (value !== undefined) { + + if (value === null) { + jQuery.removeAttr(elem, name); + return; + + } else if (hooks && "set" in hooks && notxml && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + + } else { + elem.setAttribute(name, "" + value); + return value; + } + + } else if (hooks && "get" in hooks && notxml && (ret = hooks.get(elem, name)) !== null) { + return ret; + + } else { + + ret = elem.getAttribute(name); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr:function (elem, value) { + var propName, attrNames, name, l, isBool, + i = 0; + + if (value && elem.nodeType === 1) { + attrNames = value.toLowerCase().split(rspace); + l = attrNames.length; + + for (; i < l; i++) { + name = attrNames[ i ]; + + if (name) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test(name); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if (!isBool) { + jQuery.attr(elem, name, ""); + } + elem.removeAttribute(getSetAttribute ? name : propName); + + // Set corresponding property to false for boolean attributes + if (isBool && propName in elem) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks:{ + type:{ + set:function (elem, value) { + // We can't allow the type property to be changed (since it causes problems in IE) + if (rtype.test(elem.nodeName) && elem.parentNode) { + jQuery.error("type property can't be changed"); + } else if (!jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input")) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute("type", value); + if (val) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value:{ + get:function (elem, name) { + if (nodeHook && jQuery.nodeName(elem, "button")) { + return nodeHook.get(elem, name); + } + return name in elem ? + elem.value : + null; + }, + set:function (elem, value, name) { + if (nodeHook && jQuery.nodeName(elem, "button")) { + return nodeHook.set(elem, value, name); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix:{ + tabindex:"tabIndex", + readonly:"readOnly", + "for":"htmlFor", + "class":"className", + maxlength:"maxLength", + cellspacing:"cellSpacing", + cellpadding:"cellPadding", + rowspan:"rowSpan", + colspan:"colSpan", + usemap:"useMap", + frameborder:"frameBorder", + contenteditable:"contentEditable" + }, + + prop:function (elem, name, value) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + if (notxml) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if (value !== undefined) { + if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks:{ + tabIndex:{ + get:function (elem) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt(attributeNode.value, 10) : + rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href ? + 0 : + undefined; + } + } + } + }); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) + jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes + boolHook = { + get:function (elem, name) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop(elem, name); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set:function (elem, value, name) { + var propName; + if (value === false) { + // Remove boolean attributes when set to false + jQuery.removeAttr(elem, name); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if (propName in elem) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute(name, name.toLowerCase()); + } + return name; + } + }; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute + if (!getSetAttribute) { + + fixSpecified = { + name:true, + id:true, + coords:true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get:function (elem, name) { + var ret; + ret = elem.getAttributeNode(name); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set:function (elem, value, name) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode(name); + if (!ret) { + ret = document.createAttribute(name); + elem.setAttributeNode(ret); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function (i, name) { + jQuery.attrHooks[ name ] = jQuery.extend(jQuery.attrHooks[ name ], { + set:function (elem, value) { + if (value === "") { + elem.setAttribute(name, "auto"); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get:nodeHook.get, + set:function (elem, value, name) { + if (value === "") { + value = "false"; + } + nodeHook.set(elem, value, name); + } + }; + } + + +// Some attributes require a special call on IE + if (!jQuery.support.hrefNormalized) { + jQuery.each([ "href", "src", "width", "height" ], function (i, name) { + jQuery.attrHooks[ name ] = jQuery.extend(jQuery.attrHooks[ name ], { + get:function (elem) { + var ret = elem.getAttribute(name, 2); + return ret === null ? undefined : ret; + } + }); + }); + } + + if (!jQuery.support.style) { + jQuery.attrHooks.style = { + get:function (elem) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set:function (elem, value) { + return ( elem.style.cssText = "" + value ); + } + }; + } + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it + if (!jQuery.support.optSelected) { + jQuery.propHooks.selected = jQuery.extend(jQuery.propHooks.selected, { + get:function (elem) { + var parent = elem.parentNode; + + if (parent) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if (parent.parentNode) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); + } + +// IE6/7 call enctype encoding + if (!jQuery.support.enctype) { + jQuery.propFix.enctype = "encoding"; + } + +// Radios and checkboxes getter/setter + if (!jQuery.support.checkOn) { + jQuery.each([ "radio", "checkbox" ], function () { + jQuery.valHooks[ this ] = { + get:function (elem) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); + } + jQuery.each([ "radio", "checkbox" ], function () { + jQuery.valHooks[ this ] = jQuery.extend(jQuery.valHooks[ this ], { + set:function (elem, value) { + if (jQuery.isArray(value)) { + return ( elem.checked = jQuery.inArray(jQuery(elem).val(), value) >= 0 ); + } + } + }); + }); + + + var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function (selector) { + var quick = rquickIs.exec(selector); + if (quick) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp("(?:^|\\s)" + quick[3] + "(?:\\s|$)"); + } + return quick; + }, + quickIs = function (elem, m) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test((attrs[ "class" ] || {}).value)) + ); + }, + hoverHack = function (events) { + return jQuery.event.special.hover ? events : events.replace(rhoverHack, "mouseenter$1 mouseleave$1"); + }; + + /* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ + jQuery.event = { + + add:function (elem, types, handler, data, selector) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if (handler.handler) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if (!handler.guid) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if (!events) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if (!eventHandle) { + elemData.handle = eventHandle = function (e) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply(eventHandle.elem, arguments) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim(hoverHack(types)).split(" "); + for (t = 0; t < types.length; t++) { + + tns = rtypenamespace.exec(types[t]) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split(".").sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type:type, + origType:tns[1], + data:data, + handler:handler, + guid:handler.guid, + selector:selector, + quick:selector && quickParse(selector), + namespace:namespaces.join(".") + }, handleObjIn); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if (!handlers) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { + // Bind the global event handler to the element + if (elem.addEventListener) { + elem.addEventListener(type, eventHandle, false); + + } else if (elem.attachEvent) { + elem.attachEvent("on" + type, eventHandle); + } + } + } + + if (special.add) { + special.add.call(elem, handleObj); + + if (!handleObj.handler.guid) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if (selector) { + handlers.splice(handlers.delegateCount++, 0, handleObj); + } else { + handlers.push(handleObj); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global:{}, + + // Detach an event or set of events from an element + remove:function (elem, types, handler, selector, mappedTypes) { + + var elemData = jQuery.hasData(elem) && jQuery._data(elem), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if (!elemData || !(events = elemData.events)) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim(hoverHack(types || "")).split(" "); + for (t = 0; t < types.length; t++) { + tns = rtypenamespace.exec(types[t]) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if (!type) { + for (type in events) { + jQuery.event.remove(elem, type + types[ t ], handler, selector, true); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for (j = 0; j < eventType.length; j++) { + handleObj = eventType[ j ]; + + if (( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test(handleObj.namespace) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector )) { + eventType.splice(j--, 1); + + if (handleObj.selector) { + eventType.delegateCount--; + } + if (special.remove) { + special.remove.call(elem, handleObj); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if (eventType.length === 0 && origCount !== eventType.length) { + if (!special.teardown || special.teardown.call(elem, namespaces) === false) { + jQuery.removeEvent(elem, type, elemData.handle); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if (jQuery.isEmptyObject(events)) { + handle = elemData.handle; + if (handle) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData(elem, [ "events", "handle" ], true); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent:{ + "getData":true, + "setData":true, + "changeData":true + }, + + trigger:function (event, data, elem, onlyHandlers) { + // Don't do events on text and comment nodes + if (elem && (elem.nodeType === 3 || elem.nodeType === 8)) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if (rfocusMorph.test(type + jQuery.event.triggered)) { + return; + } + + if (type.indexOf("!") >= 0) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if (type.indexOf(".") >= 0) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ((!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ]) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event(type, event) : + // Just the event type (string) + new jQuery.Event(type); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf(":") < 0 ? "on" + type : ""; + + // Handle a global trigger + if (!elem) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for (i in cache) { + if (cache[ i ].events && cache[ i ].events[ type ]) { + jQuery.event.trigger(event, data, cache[ i ].handle.elem, true); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if (!event.target) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray(data) : []; + data.unshift(event); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if (special.trigger && special.trigger.apply(elem, data) === false) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [ + [ elem, special.bindType || type ] + ]; + if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test(bubbleType + type) ? elem : elem.parentNode; + old = null; + for (; cur; cur = cur.parentNode) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if (old && old === elem.ownerDocument) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for (i = 0; i < eventPath.length && !event.isPropagationStopped(); i++) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data(cur, "events") || {} )[ event.type ] && jQuery._data(cur, "handle"); + if (handle) { + handle.apply(cur, data); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if (handle && jQuery.acceptData(cur) && handle.apply(cur, data) === false) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if (!onlyHandlers && !event.isDefaultPrevented()) { + + if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && + !(type === "click" && jQuery.nodeName(elem, "a")) && jQuery.acceptData(elem)) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if (ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow(elem)) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if (old) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if (old) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch:function (event) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix(event || window.event); + + var handlers = ( (jQuery._data(this, "events") || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call(arguments, 0), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if (special.preDispatch && special.preDispatch.call(this, event) === false) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if (delegateCount && !(event.button && event.type === "click")) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for (cur = event.target; cur != this; cur = cur.parentNode || this) { + + // Don't process events on disabled elements (#6911, #8165) + if (cur.disabled !== true) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for (i = 0; i < delegateCount; i++) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if (selMatch[ sel ] === undefined) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs(cur, handleObj.quick) : jqcur.is(sel) + ); + } + if (selMatch[ sel ]) { + matches.push(handleObj); + } + } + if (matches.length) { + handlerQueue.push({ elem:cur, matches:matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if (handlers.length > delegateCount) { + handlerQueue.push({ elem:this, matches:handlers.slice(delegateCount) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for (i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply(matched.elem, args); + + if (ret !== undefined) { + event.result = ret; + if (ret === false) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if (special.postDispatch) { + special.postDispatch.call(this, event); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks:{}, + + keyHooks:{ + props:"char charCode key keyCode".split(" "), + filter:function (event, original) { + + // Add which for key events + if (event.which == null) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks:{ + props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter:function (event, original) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if (event.pageX == null && original.clientX != null) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if (!event.relatedTarget && fromElement) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if (!event.which && button !== undefined) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix:function (event) { + if (event[ jQuery.expando ]) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; + + event = jQuery.Event(originalEvent); + + for (i = copy.length; i;) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if (!event.target) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if (event.target.nodeType === 3) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if (event.metaKey === undefined) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter ? fixHook.filter(event, originalEvent) : event; + }, + + special:{ + ready:{ + // Make sure the ready event is setup + setup:jQuery.bindReady + }, + + load:{ + // Prevent triggered image.load events from bubbling to window.load + noBubble:true + }, + + focus:{ + delegateType:"focusin" + }, + blur:{ + delegateType:"focusout" + }, + + beforeunload:{ + setup:function (data, namespaces, eventHandle) { + // We only want to do this special case on windows + if (jQuery.isWindow(this)) { + this.onbeforeunload = eventHandle; + } + }, + + teardown:function (namespaces, eventHandle) { + if (this.onbeforeunload === eventHandle) { + this.onbeforeunload = null; + } + } + } + }, + + simulate:function (type, elem, event, bubble) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type:type, + isSimulated:true, + originalEvent:{} + } + ); + if (bubble) { + jQuery.event.trigger(e, null, elem); + } else { + jQuery.event.dispatch.call(elem, e); + } + if (e.isDefaultPrevented()) { + event.preventDefault(); + } + } + }; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. + jQuery.event.handle = jQuery.event.dispatch; + + jQuery.removeEvent = document.removeEventListener ? + function (elem, type, handle) { + if (elem.removeEventListener) { + elem.removeEventListener(type, handle, false); + } + } : + function (elem, type, handle) { + if (elem.detachEvent) { + elem.detachEvent("on" + type, handle); + } + }; + + jQuery.Event = function (src, props) { + // Allow instantiation without the 'new' keyword + if (!(this instanceof jQuery.Event)) { + return new jQuery.Event(src, props); + } + + // Event object + if (src && src.type) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if (props) { + jQuery.extend(this, props); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; + }; + + function returnFalse() { + return false; + } + + function returnTrue() { + return true; + } + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + preventDefault:function () { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if (!e) { + return; + } + + // if preventDefault exists run it on the original event + if (e.preventDefault) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation:function () { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if (!e) { + return; + } + // if stopPropagation exists run it on the original event + if (e.stopPropagation) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation:function () { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented:returnFalse, + isPropagationStopped:returnFalse, + isImmediatePropagationStopped:returnFalse + }; + +// Create mouseenter/leave events using mouseover/out and event-time checks + jQuery.each({ + mouseenter:"mouseover", + mouseleave:"mouseout" + }, function (orig, fix) { + jQuery.event.special[ orig ] = { + delegateType:fix, + bindType:fix, + + handle:function (event) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if (!related || (related !== target && !jQuery.contains(target, related))) { + event.type = handleObj.origType; + ret = handleObj.handler.apply(this, arguments); + event.type = fix; + } + return ret; + } + }; + }); + +// IE submit delegation + if (!jQuery.support.submitBubbles) { + + jQuery.event.special.submit = { + setup:function () { + // Only need this for delegated form submit events + if (jQuery.nodeName(this, "form")) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add(this, "click._submit keypress._submit", function (e) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName(elem, "input") || jQuery.nodeName(elem, "button") ? elem.form : undefined; + if (form && !form._submit_attached) { + jQuery.event.add(form, "submit._submit", function (event) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch:function (event) { + // If form was submitted by the user, bubble the event up the tree + if (event._submit_bubble) { + delete event._submit_bubble; + if (this.parentNode && !event.isTrigger) { + jQuery.event.simulate("submit", this.parentNode, event, true); + } + } + }, + + teardown:function () { + // Only need this for delegated form submit events + if (jQuery.nodeName(this, "form")) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove(this, "._submit"); + } + }; + } + +// IE change delegation and checkbox/radio fix + if (!jQuery.support.changeBubbles) { + + jQuery.event.special.change = { + + setup:function () { + + if (rformElems.test(this.nodeName)) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if (this.type === "checkbox" || this.type === "radio") { + jQuery.event.add(this, "propertychange._change", function (event) { + if (event.originalEvent.propertyName === "checked") { + this._just_changed = true; + } + }); + jQuery.event.add(this, "click._change", function (event) { + if (this._just_changed && !event.isTrigger) { + this._just_changed = false; + jQuery.event.simulate("change", this, event, true); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add(this, "beforeactivate._change", function (e) { + var elem = e.target; + + if (rformElems.test(elem.nodeName) && !elem._change_attached) { + jQuery.event.add(elem, "change._change", function (event) { + if (this.parentNode && !event.isSimulated && !event.isTrigger) { + jQuery.event.simulate("change", this.parentNode, event, true); + } + }); + elem._change_attached = true; + } + }); + }, + + handle:function (event) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if (this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox")) { + return event.handleObj.handler.apply(this, arguments); + } + }, + + teardown:function () { + jQuery.event.remove(this, "._change"); + + return rformElems.test(this.nodeName); + } + }; + } + +// Create "bubbling" focus and blur events + if (!jQuery.support.focusinBubbles) { + jQuery.each({ focus:"focusin", blur:"focusout" }, function (orig, fix) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function (event) { + jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true); + }; + + jQuery.event.special[ fix ] = { + setup:function () { + if (attaches++ === 0) { + document.addEventListener(orig, handler, true); + } + }, + teardown:function () { + if (--attaches === 0) { + document.removeEventListener(orig, handler, true); + } + } + }; + }); + } + + jQuery.fn.extend({ + + on:function (types, selector, data, fn, /*INTERNAL*/ one) { + var origFn, type; + + // Types can be a map of types/handlers + if (typeof types === "object") { + // ( types-Object, selector, data ) + if (typeof selector !== "string") { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for (type in types) { + this.on(type, selector, data, types[ type ], one); + } + return this; + } + + if (data == null && fn == null) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if (fn == null) { + if (typeof selector === "string") { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if (fn === false) { + fn = returnFalse; + } else if (!fn) { + return this; + } + + if (one === 1) { + origFn = fn; + fn = function (event) { + // Can use an empty set, since event contains the info + jQuery().off(event); + return origFn.apply(this, arguments); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each(function () { + jQuery.event.add(this, types, fn, data, selector); + }); + }, + one:function (types, selector, data, fn) { + return this.on(types, selector, data, fn, 1); + }, + off:function (types, selector, fn) { + if (types && types.preventDefault && types.handleObj) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery(types.delegateTarget).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if (typeof types === "object") { + // ( types-object [, selector] ) + for (var type in types) { + this.off(type, selector, types[ type ]); + } + return this; + } + if (selector === false || typeof selector === "function") { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if (fn === false) { + fn = returnFalse; + } + return this.each(function () { + jQuery.event.remove(this, types, fn, selector); + }); + }, + + bind:function (types, data, fn) { + return this.on(types, null, data, fn); + }, + unbind:function (types, fn) { + return this.off(types, null, fn); + }, + + live:function (types, data, fn) { + jQuery(this.context).on(types, this.selector, data, fn); + return this; + }, + die:function (types, fn) { + jQuery(this.context).off(types, this.selector || "**", fn); + return this; + }, + + delegate:function (selector, types, data, fn) { + return this.on(types, selector, data, fn); + }, + undelegate:function (selector, types, fn) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1 ? this.off(selector, "**") : this.off(types, selector, fn); + }, + + trigger:function (type, data) { + return this.each(function () { + jQuery.event.trigger(type, data, this); + }); + }, + triggerHandler:function (type, data) { + if (this[0]) { + return jQuery.event.trigger(type, data, this[0], true); + } + }, + + toggle:function (fn) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function (event) { + // Figure out which function to execute + var lastToggle = ( jQuery._data(this, "lastToggle" + fn.guid) || 0 ) % i; + jQuery._data(this, "lastToggle" + fn.guid, lastToggle + 1); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply(this, arguments) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while (i < args.length) { + args[ i++ ].guid = guid; + } + + return this.click(toggler); + }, + + hover:function (fnOver, fnOut) { + return this.mouseenter(fnOver).mouseleave(fnOut || fnOver); + } + }); + + jQuery.each(("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function (i, name) { + + // Handle event binding + jQuery.fn[ name ] = function (data, fn) { + if (fn == null) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on(name, null, data, fn) : + this.trigger(name); + }; + + if (jQuery.attrFn) { + jQuery.attrFn[ name ] = true; + } + + if (rkeyEvent.test(name)) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if (rmouseEvent.test(name)) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } + }); + + + /*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + (function () { + + var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. + [0, 0].sort(function () { + baseHasDuplicate = false; + return 0; + }); + + var Sizzle = function (selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if (context.nodeType !== 1 && context.nodeType !== 9) { + return []; + } + + if (!selector || typeof selector !== "string") { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML(context), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if (m) { + soFar = m[3]; + + parts.push(m[1]); + + if (m[2]) { + extra = m[3]; + break; + } + } + } while (m); + + if (parts.length > 1 && origPOS.exec(selector)) { + + if (parts.length === 2 && Expr.relative[ parts[0] ]) { + set = posProcess(parts[0] + parts[1], context, seed); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle(parts.shift(), context); + + while (parts.length) { + selector = parts.shift(); + + if (Expr.relative[ selector ]) { + selector += parts.shift(); + } + + set = posProcess(selector, set, seed); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if (!seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1])) { + + ret = Sizzle.find(parts.shift(), context, contextXML); + context = ret.expr ? + Sizzle.filter(ret.expr, ret.set)[0] : + ret.set[0]; + } + + if (context) { + ret = seed ? + { expr:parts.pop(), set:makeArray(seed) } : + Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML); + + set = ret.expr ? + Sizzle.filter(ret.expr, ret.set) : + ret.set; + + if (parts.length > 0) { + checkSet = makeArray(set); + + } else { + prune = false; + } + + while (parts.length) { + cur = parts.pop(); + pop = cur; + + if (!Expr.relative[ cur ]) { + cur = ""; + } else { + pop = parts.pop(); + } + + if (pop == null) { + pop = context; + } + + Expr.relative[ cur ](checkSet, pop, contextXML); + } + + } else { + checkSet = parts = []; + } + } + + if (!checkSet) { + checkSet = set; + } + + if (!checkSet) { + Sizzle.error(cur || selector); + } + + if (toString.call(checkSet) === "[object Array]") { + if (!prune) { + results.push.apply(results, checkSet); + + } else if (context && context.nodeType === 1) { + for (i = 0; checkSet[i] != null; i++) { + if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) { + results.push(set[i]); + } + } + + } else { + for (i = 0; checkSet[i] != null; i++) { + if (checkSet[i] && checkSet[i].nodeType === 1) { + results.push(set[i]); + } + } + } + + } else { + makeArray(checkSet, results); + } + + if (extra) { + Sizzle(extra, origContext, results, seed); + Sizzle.uniqueSort(results); + } + + return results; + }; + + Sizzle.uniqueSort = function (results) { + if (sortOrder) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if (hasDuplicate) { + for (var i = 1; i < results.length; i++) { + if (results[i] === results[ i - 1 ]) { + results.splice(i--, 1); + } + } + } + } + + return results; + }; + + Sizzle.matches = function (expr, set) { + return Sizzle(expr, null, null, set); + }; + + Sizzle.matchesSelector = function (node, expr) { + return Sizzle(expr, null, null, [node]).length > 0; + }; + + Sizzle.find = function (expr, context, isXML) { + var set, i, len, match, type, left; + + if (!expr) { + return []; + } + + for (i = 0, len = Expr.order.length; i < len; i++) { + type = Expr.order[i]; + + if ((match = Expr.leftMatch[ type ].exec(expr))) { + left = match[1]; + match.splice(1, 1); + + if (left.substr(left.length - 1) !== "\\") { + match[1] = (match[1] || "").replace(rBackslash, ""); + set = Expr.find[ type ](match, context, isXML); + + if (set != null) { + expr = expr.replace(Expr.match[ type ], ""); + break; + } + } + } + } + + if (!set) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName("*") : + []; + } + + return { set:set, expr:expr }; + }; + + Sizzle.filter = function (expr, set, inplace, not) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while (expr && set.length) { + for (type in Expr.filter) { + if ((match = Expr.leftMatch[ type ].exec(expr)) != null && match[2]) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1, 1); + + if (left.substr(left.length - 1) === "\\") { + continue; + } + + if (curLoop === result) { + result = []; + } + + if (Expr.preFilter[ type ]) { + match = Expr.preFilter[ type ](match, curLoop, inplace, result, not, isXMLFilter); + + if (!match) { + anyFound = found = true; + + } else if (match === true) { + continue; + } + } + + if (match) { + for (i = 0; (item = curLoop[i]) != null; i++) { + if (item) { + found = filter(item, match, i, curLoop); + pass = not ^ found; + + if (inplace && found != null) { + if (pass) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if (pass) { + result.push(item); + anyFound = true; + } + } + } + } + + if (found !== undefined) { + if (!inplace) { + curLoop = result; + } + + expr = expr.replace(Expr.match[ type ], ""); + + if (!anyFound) { + return []; + } + + break; + } + } + } + + // Improper expression + if (expr === old) { + if (anyFound == null) { + Sizzle.error(expr); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; + }; + + Sizzle.error = function (msg) { + throw new Error("Syntax error, unrecognized expression: " + msg); + }; + + /** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ + var getText = Sizzle.getText = function (elem) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if (nodeType) { + if (nodeType === 1 || nodeType === 9 || nodeType === 11) { + // Use textContent || innerText for elements + if (typeof elem.textContent === 'string') { + return elem.textContent; + } else if (typeof elem.innerText === 'string') { + // Replace IE's carriage returns + return elem.innerText.replace(rReturn, ''); + } else { + // Traverse it's children + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText(elem); + } + } + } else if (nodeType === 3 || nodeType === 4) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for (i = 0; (node = elem[i]); i++) { + // Do not traverse comment nodes + if (node.nodeType !== 8) { + ret += getText(node); + } + } + } + return ret; + }; + + var Expr = Sizzle.selectors = { + order:[ "ID", "NAME", "TAG" ], + + match:{ + ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch:{}, + + attrMap:{ + "class":"className", + "for":"htmlFor" + }, + + attrHandle:{ + href:function (elem) { + return elem.getAttribute("href"); + }, + type:function (elem) { + return elem.getAttribute("type"); + } + }, + + relative:{ + "+":function (checkSet, part) { + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if (isTag) { + part = part.toLowerCase(); + } + + for (var i = 0, l = checkSet.length, elem; i < l; i++) { + if ((elem = checkSet[i])) { + while ((elem = elem.previousSibling) && elem.nodeType !== 1) { + } + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if (isPartStrNotTag) { + Sizzle.filter(part, checkSet, true); + } + }, + + ">":function (checkSet, part) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if (isPartStr && !rNonWord.test(part)) { + part = part.toLowerCase(); + + for (; i < l; i++) { + elem = checkSet[i]; + + if (elem) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for (; i < l; i++) { + elem = checkSet[i]; + + if (elem) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if (isPartStr) { + Sizzle.filter(part, checkSet, true); + } + } + }, + + "":function (checkSet, part, isXML) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if (typeof part === "string" && !rNonWord.test(part)) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + + "~":function (checkSet, part, isXML) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if (typeof part === "string" && !rNonWord.test(part)) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + + find:{ + ID:function (match, context, isXML) { + if (typeof context.getElementById !== "undefined" && !isXML) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME:function (match, context) { + if (typeof context.getElementsByName !== "undefined") { + var ret = [], + results = context.getElementsByName(match[1]); + + for (var i = 0, l = results.length; i < l; i++) { + if (results[i].getAttribute("name") === match[1]) { + ret.push(results[i]); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG:function (match, context) { + if (typeof context.getElementsByTagName !== "undefined") { + return context.getElementsByTagName(match[1]); + } + } + }, + preFilter:{ + CLASS:function (match, curLoop, inplace, result, not, isXML) { + match = " " + match[1].replace(rBackslash, "") + " "; + + if (isXML) { + return match; + } + + for (var i = 0, elem; (elem = curLoop[i]) != null; i++) { + if (elem) { + if (not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0)) { + if (!inplace) { + result.push(elem); + } + + } else if (inplace) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID:function (match) { + return match[1].replace(rBackslash, ""); + }, + + TAG:function (match, curLoop) { + return match[1].replace(rBackslash, "").toLowerCase(); + }, + + CHILD:function (match) { + if (match[1] === "nth") { + if (!match[2]) { + Sizzle.error(match[0]); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test(match[2]) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if (match[2]) { + Sizzle.error(match[0]); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR:function (match, curLoop, inplace, result, not, isXML) { + var name = match[1] = match[1].replace(rBackslash, ""); + + if (!isXML && Expr.attrMap[name]) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace(rBackslash, ""); + + if (match[2] === "~=") { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO:function (match, curLoop, inplace, result, not) { + if (match[1] === "not") { + // If we're dealing with a complex expression, or a simple one + if (( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3])) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if (!inplace) { + result.push.apply(result, ret); + } + + return false; + } + + } else if (Expr.match.POS.test(match[0]) || Expr.match.CHILD.test(match[0])) { + return true; + } + + return match; + }, + + POS:function (match) { + match.unshift(true); + + return match; + } + }, + + filters:{ + enabled:function (elem) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled:function (elem) { + return elem.disabled === true; + }, + + checked:function (elem) { + return elem.checked === true; + }, + + selected:function (elem) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if (elem.parentNode) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent:function (elem) { + return !!elem.firstChild; + }, + + empty:function (elem) { + return !elem.firstChild; + }, + + has:function (elem, i, match) { + return !!Sizzle(match[3], elem).length; + }, + + header:function (elem) { + return (/h\d/i).test(elem.nodeName); + }, + + text:function (elem) { + var attr = elem.getAttribute("type"), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio:function (elem) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox:function (elem) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file:function (elem) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password:function (elem) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit:function (elem) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image:function (elem) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset:function (elem) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button:function (elem) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input:function (elem) { + return (/input|select|textarea|button/i).test(elem.nodeName); + }, + + focus:function (elem) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters:{ + first:function (elem, i) { + return i === 0; + }, + + last:function (elem, i, match, array) { + return i === array.length - 1; + }, + + even:function (elem, i) { + return i % 2 === 0; + }, + + odd:function (elem, i) { + return i % 2 === 1; + }, + + lt:function (elem, i, match) { + return i < match[3] - 0; + }, + + gt:function (elem, i, match) { + return i > match[3] - 0; + }, + + nth:function (elem, i, match) { + return match[3] - 0 === i; + }, + + eq:function (elem, i, match) { + return match[3] - 0 === i; + } + }, + filter:{ + PSEUDO:function (elem, match, i, array) { + var name = match[1], + filter = Expr.filters[ name ]; + + if (filter) { + return filter(elem, i, match, array); + + } else if (name === "contains") { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if (name === "not") { + var not = match[3]; + + for (var j = 0, l = not.length; j < l; j++) { + if (not[j] === elem) { + return false; + } + } + + return true; + + } else { + Sizzle.error(name); + } + }, + + CHILD:function (elem, match) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch (type) { + case "only": + case "first": + while ((node = node.previousSibling)) { + if (node.nodeType === 1) { + return false; + } + } + + if (type === "first") { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ((node = node.nextSibling)) { + if (node.nodeType === 1) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if (first === 1 && last === 0) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if (parent && (parent[ expando ] !== doneName || !elem.nodeIndex)) { + count = 0; + + for (node = parent.firstChild; node; node = node.nextSibling) { + if (node.nodeType === 1) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if (first === 0) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID:function (elem, match) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG:function (elem, match) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS:function (elem, match) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf(match) > -1; + }, + + ATTR:function (elem, match) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr(elem, name) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ](elem) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute(name), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS:function (elem, match, i, array) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if (filter) { + return filter(elem, i, match, array); + } + } + } + }; + + var origPOS = Expr.match.POS, + fescape = function (all, num) { + return "\\" + (num - 0 + 1); + }; + + for (var type in Expr.match) { + Expr.match[ type ] = new RegExp(Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source)); + Expr.leftMatch[ type ] = new RegExp(/(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape)); + } +// Expose origPOS +// "global" as in regardless of relation to brackets/parens + Expr.match.globalPOS = origPOS; + + var makeArray = function (array, results) { + array = Array.prototype.slice.call(array, 0); + + if (results) { + results.push.apply(results, array); + return results; + } + + return array; + }; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) + try { + Array.prototype.slice.call(document.documentElement.childNodes, 0)[0].nodeType; + +// Provide a fallback method if it does not work + } catch (e) { + makeArray = function (array, results) { + var i = 0, + ret = results || []; + + if (toString.call(array) === "[object Array]") { + Array.prototype.push.apply(ret, array); + + } else { + if (typeof array.length === "number") { + for (var l = array.length; i < l; i++) { + ret.push(array[i]); + } + + } else { + for (; array[i]; i++) { + ret.push(array[i]); + } + } + } + + return ret; + }; + } + + var sortOrder, siblingCheck; + + if (document.documentElement.compareDocumentPosition) { + sortOrder = function (a, b) { + if (a === b) { + hasDuplicate = true; + return 0; + } + + if (!a.compareDocumentPosition || !b.compareDocumentPosition) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + + } else { + sortOrder = function (a, b) { + // The nodes are identical, we can exit early + if (a === b) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if (a.sourceIndex && b.sourceIndex) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if (aup === bup) { + return siblingCheck(a, b); + + // If no parents were found then the nodes are disconnected + } else if (!aup) { + return -1; + + } else if (!bup) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while (cur) { + ap.unshift(cur); + cur = cur.parentNode; + } + + cur = bup; + + while (cur) { + bp.unshift(cur); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for (var i = 0; i < al && i < bl; i++) { + if (ap[i] !== bp[i]) { + return siblingCheck(ap[i], bp[i]); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck(a, bp[i], -1) : + siblingCheck(ap[i], b, 1); + }; + + siblingCheck = function (a, b, ret) { + if (a === b) { + return ret; + } + + var cur = a.nextSibling; + + while (cur) { + if (cur === b) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; + } + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) + (function () { + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore(form, root.firstChild); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if (document.getElementById(id)) { + Expr.find.ID = function (match, context, isXML) { + if (typeof context.getElementById !== "undefined" && !isXML) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function (elem, match) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild(form); + + // release memory in IE + root = form = null; + })(); + + (function () { + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild(document.createComment("")); + + // Make sure no comments are found + if (div.getElementsByTagName("*").length > 0) { + Expr.find.TAG = function (match, context) { + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if (match[1] === "*") { + var tmp = []; + + for (var i = 0; results[i]; i++) { + if (results[i].nodeType === 1) { + tmp.push(results[i]); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if (div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#") { + + Expr.attrHandle.href = function (elem) { + return elem.getAttribute("href", 2); + }; + } + + // release memory in IE + div = null; + })(); + + if (document.querySelectorAll) { + (function () { + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if (div.querySelectorAll && div.querySelectorAll(".TEST").length === 0) { + return; + } + + Sizzle = function (query, context, extra, seed) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if (!seed && !Sizzle.isXML(context)) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(query); + + if (match && (context.nodeType === 1 || context.nodeType === 9)) { + // Speed-up: Sizzle("TAG") + if (match[1]) { + return makeArray(context.getElementsByTagName(query), extra); + + // Speed-up: Sizzle(".CLASS") + } else if (match[2] && Expr.find.CLASS && context.getElementsByClassName) { + return makeArray(context.getElementsByClassName(match[2]), extra); + } + } + + if (context.nodeType === 9) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if (query === "body" && context.body) { + return makeArray([ context.body ], extra); + + // Speed-up: Sizzle("#ID") + } else if (match && match[3]) { + var elem = context.getElementById(match[3]); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE and Opera return items + // by name instead of ID + if (elem.id === match[3]) { + return makeArray([ elem ], extra); + } + + } else { + return makeArray([], extra); + } + } + + try { + return makeArray(context.querySelectorAll(query), extra); + } catch (qsaError) { + } + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if (context.nodeType === 1 && context.nodeName.toLowerCase() !== "object") { + var oldContext = context, + old = context.getAttribute("id"), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test(query); + + if (!old) { + context.setAttribute("id", nid); + } else { + nid = nid.replace(/'/g, "\\$&"); + } + if (relativeHierarchySelector && hasParent) { + context = context.parentNode; + } + + try { + if (!relativeHierarchySelector || hasParent) { + return makeArray(context.querySelectorAll("[id='" + nid + "'] " + query), extra); + } + + } catch (pseudoError) { + } finally { + if (!old) { + oldContext.removeAttribute("id"); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for (var prop in oldSizzle) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); + } + + (function () { + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if (matches) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call(document.createElement("div"), "div"), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call(document.documentElement, "[test!='']:sizzle"); + + } catch (pseudoError) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function (node, expr) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if (!Sizzle.isXML(node)) { + try { + if (pseudoWorks || !Expr.match.PSEUDO.test(expr) && !/!=/.test(expr)) { + var ret = matches.call(node, expr); + + // IE 9's matchesSelector returns false on disconnected nodes + if (ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11) { + return ret; + } + } + } catch (e) { + } + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } + })(); + + (function () { + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if (!div.getElementsByClassName || div.getElementsByClassName("e").length === 0) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if (div.getElementsByClassName("e").length === 1) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function (match, context, isXML) { + if (typeof context.getElementsByClassName !== "undefined" && !isXML) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; + })(); + + function dirNodeCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { + for (var i = 0, l = checkSet.length; i < l; i++) { + var elem = checkSet[i]; + + if (elem) { + var match = false; + + elem = elem[dir]; + + while (elem) { + if (elem[ expando ] === doneName) { + match = checkSet[elem.sizset]; + break; + } + + if (elem.nodeType === 1 && !isXML) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if (elem.nodeName.toLowerCase() === cur) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } + } + + function dirCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { + for (var i = 0, l = checkSet.length; i < l; i++) { + var elem = checkSet[i]; + + if (elem) { + var match = false; + + elem = elem[dir]; + + while (elem) { + if (elem[ expando ] === doneName) { + match = checkSet[elem.sizset]; + break; + } + + if (elem.nodeType === 1) { + if (!isXML) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if (typeof cur !== "string") { + if (elem === cur) { + match = true; + break; + } + + } else if (Sizzle.filter(cur, [elem]).length > 0) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } + } + + if (document.documentElement.contains) { + Sizzle.contains = function (a, b) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + + } else if (document.documentElement.compareDocumentPosition) { + Sizzle.contains = function (a, b) { + return !!(a.compareDocumentPosition(b) & 16); + }; + + } else { + Sizzle.contains = function () { + return false; + }; + } + + Sizzle.isXML = function (elem) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; + }; + + var posProcess = function (selector, context, seed) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ((match = Expr.match.PSEUDO.exec(selector))) { + later += match[0]; + selector = selector.replace(Expr.match.PSEUDO, ""); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for (var i = 0, l = root.length; i < l; i++) { + Sizzle(selector, root[i], tmpSet, seed); + } + + return Sizzle.filter(later, tmpSet); + }; + +// EXPOSE +// Override sizzle attribute retrieval + Sizzle.attr = jQuery.attr; + Sizzle.selectors.attrMap = {}; + jQuery.find = Sizzle; + jQuery.expr = Sizzle.selectors; + jQuery.expr[":"] = jQuery.expr.filters; + jQuery.unique = Sizzle.uniqueSort; + jQuery.text = Sizzle.getText; + jQuery.isXMLDoc = Sizzle.isXML; + jQuery.contains = Sizzle.contains; + + + })(); + + + var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children:true, + contents:true, + next:true, + prev:true + }; + + jQuery.fn.extend({ + find:function (selector) { + var self = this, + i, l; + + if (typeof selector !== "string") { + return jQuery(selector).filter(function () { + for (i = 0, l = self.length; i < l; i++) { + if (jQuery.contains(self[ i ], this)) { + return true; + } + } + }); + } + + var ret = this.pushStack("", "find", selector), + length, n, r; + + for (i = 0, l = this.length; i < l; i++) { + length = ret.length; + jQuery.find(selector, this[i], ret); + + if (i > 0) { + // Make sure that the results are unique + for (n = length; n < ret.length; n++) { + for (r = 0; r < length; r++) { + if (ret[r] === ret[n]) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has:function (target) { + var targets = jQuery(target); + return this.filter(function () { + for (var i = 0, l = targets.length; i < l; i++) { + if (jQuery.contains(this, targets[i])) { + return true; + } + } + }); + }, + + not:function (selector) { + return this.pushStack(winnow(this, selector, false), "not", selector); + }, + + filter:function (selector) { + return this.pushStack(winnow(this, selector, true), "filter", selector); + }, + + is:function (selector) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test(selector) ? + jQuery(selector, this.context).index(this[0]) >= 0 : + jQuery.filter(selector, this).length > 0 : + this.filter(selector).length > 0 ); + }, + + closest:function (selectors, context) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if (jQuery.isArray(selectors)) { + var level = 1; + + while (cur && cur.ownerDocument && cur !== context) { + for (i = 0; i < selectors.length; i++) { + + if (jQuery(cur).is(selectors[ i ])) { + ret.push({ selector:selectors[ i ], elem:cur, level:level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test(selectors) || typeof selectors !== "string" ? + jQuery(selectors, context || this.context) : + 0; + + for (i = 0, l = this.length; i < l; i++) { + cur = this[i]; + + while (cur) { + if (pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors)) { + ret.push(cur); + break; + + } else { + cur = cur.parentNode; + if (!cur || !cur.ownerDocument || cur === context || cur.nodeType === 11) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack(ret, "closest", selectors); + }, + + // Determine the position of an element within + // the matched set of elements + index:function (elem) { + + // No argument, return index in parent + if (!elem) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if (typeof elem === "string") { + return jQuery.inArray(this[0], jQuery(elem)); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this); + }, + + add:function (selector, context) { + var set = typeof selector === "string" ? + jQuery(selector, context) : + jQuery.makeArray(selector && selector.nodeType ? [ selector ] : selector), + all = jQuery.merge(this.get(), set); + + return this.pushStack(isDisconnected(set[0]) || isDisconnected(all[0]) ? + all : + jQuery.unique(all)); + }, + + andSelf:function () { + return this.add(this.prevObject); + } + }); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). + function isDisconnected(node) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; + } + + jQuery.each({ + parent:function (elem) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents:function (elem) { + return jQuery.dir(elem, "parentNode"); + }, + parentsUntil:function (elem, i, until) { + return jQuery.dir(elem, "parentNode", until); + }, + next:function (elem) { + return jQuery.nth(elem, 2, "nextSibling"); + }, + prev:function (elem) { + return jQuery.nth(elem, 2, "previousSibling"); + }, + nextAll:function (elem) { + return jQuery.dir(elem, "nextSibling"); + }, + prevAll:function (elem) { + return jQuery.dir(elem, "previousSibling"); + }, + nextUntil:function (elem, i, until) { + return jQuery.dir(elem, "nextSibling", until); + }, + prevUntil:function (elem, i, until) { + return jQuery.dir(elem, "previousSibling", until); + }, + siblings:function (elem) { + return jQuery.sibling(( elem.parentNode || {} ).firstChild, elem); + }, + children:function (elem) { + return jQuery.sibling(elem.firstChild); + }, + contents:function (elem) { + return jQuery.nodeName(elem, "iframe") ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray(elem.childNodes); + } + }, function (name, fn) { + jQuery.fn[ name ] = function (until, selector) { + var ret = jQuery.map(this, fn, until); + + if (!runtil.test(name)) { + selector = until; + } + + if (selector && typeof selector === "string") { + ret = jQuery.filter(selector, ret); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique(ret) : ret; + + if ((this.length > 1 || rmultiselector.test(selector)) && rparentsprev.test(name)) { + ret = ret.reverse(); + } + + return this.pushStack(ret, name, slice.call(arguments).join(",")); + }; + }); + + jQuery.extend({ + filter:function (expr, elems, not) { + if (not) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir:function (elem, dir, until) { + var matched = [], + cur = elem[ dir ]; + + while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery(cur).is(until))) { + if (cur.nodeType === 1) { + matched.push(cur); + } + cur = cur[dir]; + } + return matched; + }, + + nth:function (cur, result, dir, elem) { + result = result || 1; + var num = 0; + + for (; cur; cur = cur[dir]) { + if (cur.nodeType === 1 && ++num === result) { + break; + } + } + + return cur; + }, + + sibling:function (n, elem) { + var r = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + r.push(n); + } + } + + return r; + } + }); + +// Implement the identical functionality for filter and not + function winnow(elements, qualifier, keep) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if (jQuery.isFunction(qualifier)) { + return jQuery.grep(elements, function (elem, i) { + var retVal = !!qualifier.call(elem, i, elem); + return retVal === keep; + }); + + } else if (qualifier.nodeType) { + return jQuery.grep(elements, function (elem, i) { + return ( elem === qualifier ) === keep; + }); + + } else if (typeof qualifier === "string") { + var filtered = jQuery.grep(elements, function (elem) { + return elem.nodeType === 1; + }); + + if (isSimple.test(qualifier)) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter(qualifier, filtered); + } + } + + return jQuery.grep(elements, function (elem, i) { + return ( jQuery.inArray(elem, qualifier) >= 0 ) === keep; + }); + } + + + function createSafeFragment(document) { + var list = nodeNames.split("|"), + safeFrag = document.createDocumentFragment(); + + if (safeFrag.createElement) { + while (list.length) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; + } + + var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend:[ 1, "
", "
" ], + thead:[ 1, "", "
" ], + tr:[ 2, "", "
" ], + td:[ 3, "", "
" ], + col:[ 2, "", "
" ], + area:[ 1, "", "" ], + _default:[ 0, "", "" ] + }, + safeFragment = createSafeFragment(document); + + wrapMap.optgroup = wrapMap.option; + wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; + wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + + + + + +
+

{{resource_name}} API

+ +
+

{{ description }}

+ {{# explanation }} +

+ {{ explanation }} +

+ {{/ explanation }} + + {{# has_parameters? }} +

Parameters

+ + + + + + + + + {{# parameters }} + + + + + {{/ parameters }} + +
NameDescription
+ {{ name }} + + {{ description }} +
+ {{/ has_parameters? }} + + {{# requests }} +
+

Request

+ +

Headers

+
{{ request_headers_text }}
+ +

Route

+
{{ request_method }} {{ request_path }}
+ + {{# request_query_parameters_text }} +

Query Parameters

+
{{ request_query_parameters_text }}
+ {{/ request_query_parameters_text }} + + {{# request_body }} +

Body

+ +
+ {{/ request_body }} + + {{# curl }} +

cURL

+
{{ curl }}
+ {{/ curl }} + +

wURL

+ +
+
+ + + +
+ + +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+ + + {{# request_headers_hash }} +
+ + + +
+ {{/ request_headers_hash }} + +
+
+
+ + +
+ + {{# request_query_parameters_hash }} + +
+ + + +
+ {{/ request_query_parameters_hash }} + + +
+
+
+ + +
+ +
+
+
+
+ + +
+
+
+ +

Response

+

Headers

+
{{ response_headers_text }}
+ + {{# response_status }} +

Status

+
{{ response_status }}
+ {{/ response_status }} + + {{# response_body }} +

Body

+ +
+ {{/ response_body }}
- - +
+
+ {{/ requests }} +
+
+ + \ No newline at end of file diff --git a/templates/rspec_api_documentation/html_index.mustache b/templates/rspec_api_documentation/html_index.mustache index 90a513ff..e4168683 100644 --- a/templates/rspec_api_documentation/html_index.mustache +++ b/templates/rspec_api_documentation/html_index.mustache @@ -1,25 +1,25 @@ - - - API Documentation - - - -
-

API Documentation

+ + + API Documentation + + + +
+

API Documentation

- {{# sections }} -
-

{{ resource_name }}

+ {{# sections }} +
+

{{ resource_name }}

- -
- {{/ sections }} -
- - + +
+ {{/ sections }} +
+ + \ No newline at end of file