├── .gitignore ├── .rspec ├── .travis.yml ├── .yardopts ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── config.ru ├── lib ├── public │ ├── application.css │ ├── bootstrap.min.css │ ├── codemirror.css │ └── js │ │ ├── codemirror.js │ │ ├── example.js │ │ ├── jquery-1-7-2.js │ │ └── mode │ │ ├── css │ │ └── css.js │ │ ├── htmlmixed │ │ └── htmlmixed.js │ │ ├── javascript │ │ └── javascript.js │ │ └── xml │ │ └── xml.js ├── raddocs.rb ├── raddocs │ ├── app.rb │ ├── configuration.rb │ ├── middleware.rb │ └── models.rb └── views │ ├── example.haml │ ├── guide.erb │ ├── index.haml │ ├── layout.haml │ └── nav.haml ├── raddocs.gemspec └── spec ├── configuration_spec.rb ├── example_spec.rb ├── fixtures ├── authentication.md ├── guides.yml ├── index.json ├── orders │ ├── creating_an_order.json │ ├── index.txt │ └── viewing_an_order.json └── styles │ ├── extra-style.css │ └── style.css ├── guides_spec.rb ├── index_spec.rb ├── middleware_spec.rb ├── mountable_spec.rb ├── parameter_spec.rb ├── parameters_spec.rb ├── raddocs_spec.rb ├── response_fields_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # Default gitignore 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | coverage 7 | InstalledFiles 8 | lib/bundler/man 9 | pkg 10 | rdoc 11 | spec/reports 12 | test/tmp 13 | test/version_tmp 14 | tmp 15 | 16 | docs 17 | 18 | doc 19 | .yardoc 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --order random 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | rvm: 4 | - 2.2.7 5 | - 2.3.4 6 | - 2.4.1 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --plugin yard-sinatra 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem 'inch' 6 | gem 'yard' 7 | gem 'yard-sinatra' 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | raddocs (2.3.0) 5 | haml (>= 4.0) 6 | json 7 | sinatra (~> 2.0) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | addressable (2.5.1) 13 | public_suffix (~> 2.0, >= 2.0.2) 14 | capybara (2.14.4) 15 | addressable 16 | mime-types (>= 1.16) 17 | nokogiri (>= 1.3.3) 18 | rack (>= 1.0.0) 19 | rack-test (>= 0.5.4) 20 | xpath (~> 2.0) 21 | coderay (1.1.1) 22 | diff-lcs (1.3) 23 | haml (5.0.1) 24 | temple (>= 0.8.0) 25 | tilt 26 | inch (0.7.1) 27 | pry 28 | sparkr (>= 0.2.0) 29 | term-ansicolor 30 | yard (~> 0.8.7.5) 31 | json (2.1.0) 32 | kramdown (1.14.0) 33 | method_source (0.8.2) 34 | mime-types (3.1) 35 | mime-types-data (~> 3.2015) 36 | mime-types-data (3.2016.0521) 37 | mini_portile2 (2.2.0) 38 | mustermann (1.0.0) 39 | nokogiri (1.8.0) 40 | mini_portile2 (~> 2.2.0) 41 | pry (0.10.4) 42 | coderay (~> 1.1.0) 43 | method_source (~> 0.8.1) 44 | slop (~> 3.4) 45 | public_suffix (2.0.5) 46 | rack (2.0.3) 47 | rack-protection (2.0.0) 48 | rack 49 | rack-test (0.7.0) 50 | rack (>= 1.0, < 3) 51 | rake (10.5.0) 52 | rspec (3.6.0) 53 | rspec-core (~> 3.6.0) 54 | rspec-expectations (~> 3.6.0) 55 | rspec-mocks (~> 3.6.0) 56 | rspec-core (3.6.0) 57 | rspec-support (~> 3.6.0) 58 | rspec-expectations (3.6.0) 59 | diff-lcs (>= 1.2.0, < 2.0) 60 | rspec-support (~> 3.6.0) 61 | rspec-mocks (3.6.0) 62 | diff-lcs (>= 1.2.0, < 2.0) 63 | rspec-support (~> 3.6.0) 64 | rspec-support (3.6.0) 65 | sinatra (2.0.0) 66 | mustermann (~> 1.0) 67 | rack (~> 2.0) 68 | rack-protection (= 2.0.0) 69 | tilt (~> 2.0) 70 | slop (3.6.0) 71 | sparkr (0.4.1) 72 | temple (0.8.0) 73 | term-ansicolor (1.6.0) 74 | tins (~> 1.0) 75 | tilt (2.0.8) 76 | tins (1.15.0) 77 | xpath (2.1.0) 78 | nokogiri (~> 1.3) 79 | yard (0.8.7.6) 80 | yard-sinatra (1.0.0) 81 | yard (~> 0.7) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | capybara (~> 2.3) 88 | inch 89 | kramdown 90 | rack-test (~> 0.6) 91 | raddocs! 92 | rake (~> 10.0) 93 | rspec (~> 3.0) 94 | yard 95 | yard-sinatra 96 | 97 | BUNDLED WITH 98 | 1.15.3 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Eric Oestrich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis status](https://secure.travis-ci.org/smartlogic/raddocs.svg)](https://secure.travis-ci.org/smartlogic/raddocs) 2 | [![Inline Docs](http://inch-ci.org/github/smartlogic/raddocs.png)](http://inch-ci.org/github/smartlogic/raddocs) 3 | 4 | 5 | # Raddocs 6 | 7 | Raddocs is a browser for JSON outputted by the [rspec_api_documentation](http://github.com/zipmark/rspec_api_documentation) gem. 8 | 9 | ## Install 10 | 11 | `Gemfile` 12 | ```ruby 13 | gem 'raddocs' 14 | ``` 15 | 16 | `config/routes.rb` 17 | 18 | ```ruby 19 | mount Raddocs::App => "/docs" 20 | ``` 21 | 22 | Make sure RspecApiDocumentation is generating JSON: 23 | 24 | `spec/spec_helper.rb` 25 | 26 | ```ruby 27 | RspecApiDocumentation.configure do |config| 28 | config.format = :json 29 | end 30 | ``` 31 | 32 | 33 | ## Configuration 34 | 35 | `config/initializers/raddocs.rb` 36 | 37 | * `api_name` - Name of the API on the example index page 38 | * `docs_dir` - where the JSON output from rspec_api_documentation is located 39 | * `docs_mime_type` - if you use the middleware, what mime type are you serving your docs as, must be a regex. eg: `/text\/vnd.org.oestrich.raddocs\+plain/` 40 | * `include_bootstrap` - Boolean to disable including bootstrap if you are using your own css 41 | * `external_css` - Array of css files to include, with a full URL to them 42 | * `url_prefix` - Optional prefix to insert before URLs generated by Raddocs 43 | 44 | ```ruby 45 | Raddocs.configure do |config| 46 | config.docs_dir = "doc/api" 47 | end 48 | ``` 49 | 50 | ### Custom CSS 51 | 52 | You can include extra css by the config option `external_css` or add a directory to the docs dir named `styles`. Every css file in the styles dir will be included as a link element on all pages. 53 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rspec/core/rake_task" 2 | RSpec::Core::RakeTask.new(:spec) 3 | 4 | task :default => [:spec] 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'raddocs' 3 | 4 | run Raddocs::App 5 | -------------------------------------------------------------------------------- /lib/public/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 25px 0; 3 | } 4 | .container{ 5 | margin-top: 25px; 6 | } 7 | .response .body .content, .request .body .content { 8 | background-color: #f5f5f5; 9 | margin: 0; 10 | padding: 0 0 0 5px; 11 | border: 1px solid #ccc; 12 | -webkit-border-radius: 4px; 13 | -moz-border-radius: 4px; 14 | border-radius: 4px; 15 | } 16 | 17 | .form-horizontal .control-label.with-button { 18 | padding-top: 0; 19 | } 20 | 21 | .form-horizontal .control-label.with-button .text{ 22 | margin-top: 5px; 23 | line-height: 26px; 24 | } 25 | 26 | .form-horizontal .btn-small { 27 | padding: 3px 7px; 28 | vertical-align: top; 29 | margin-top: 0; 30 | } 31 | 32 | .post_body .CodeMirror-lines { 33 | background-color: white; 34 | width: 436px; 35 | border: 1px solid #CCC; 36 | -webkit-border-radius: 3px; 37 | -moz-border-radius: 3px; 38 | border-radius: 3px; 39 | } 40 | 41 | .nav-bar { 42 | font-size: 1.2em; 43 | margin-bottom: 15px; 44 | } 45 | -------------------------------------------------------------------------------- /lib/public/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | html,body{margin:0;padding:0;} 2 | h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} 3 | table{border-collapse:collapse;border-spacing:0;} 4 | ol,ul{list-style:none;} 5 | q:before,q:after,blockquote:before,blockquote:after{content:"";} 6 | html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} 7 | a:focus{outline:thin dotted;} 8 | a:hover,a:active{outline:0;} 9 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} 10 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} 11 | audio:not([controls]){display:none;} 12 | sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} 13 | sup{top:-0.5em;} 14 | sub{bottom:-0.25em;} 15 | img{border:0;-ms-interpolation-mode:bicubic;} 16 | button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} 17 | button,input{line-height:normal;*overflow:visible;} 18 | button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 19 | button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} 20 | input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} 21 | input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} 22 | textarea{overflow:auto;vertical-align:top;} 23 | html,body{background-color:#ffffff;} 24 | body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} 25 | .container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;*display:inline;} 26 | .container:after{clear:both;} 27 | .container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;*display:inline;} 28 | .container-fluid:after{clear:both;} 29 | .container-fluid>.sidebar{float:left;width:220px;} 30 | .container-fluid>.content{margin-left:240px;} 31 | a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} 32 | .pull-right{float:right;} 33 | .pull-left{float:left;} 34 | .hide{display:none;} 35 | .show{display:block;} 36 | .row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;*display:inline;} 37 | .row:after{clear:both;} 38 | [class*="span"]{display:inline;float:left;margin-left:20px;} 39 | .span1{width:40px;} 40 | .span2{width:100px;} 41 | .span3{width:160px;} 42 | .span4{width:220px;} 43 | .span5{width:280px;} 44 | .span6{width:340px;} 45 | .span7{width:400px;} 46 | .span8{width:460px;} 47 | .span9{width:520px;} 48 | .span10{width:580px;} 49 | .span11{width:640px;} 50 | .span12{width:700px;} 51 | .span13{width:760px;} 52 | .span14{width:820px;} 53 | .span15{width:880px;} 54 | .span16{width:940px;} 55 | .span17{width:1000px;} 56 | .span18{width:1060px;} 57 | .span19{width:1120px;} 58 | .span20{width:1180px;} 59 | .span21{width:1240px;} 60 | .span22{width:1300px;} 61 | .span23{width:1360px;} 62 | .span24{width:1420px;} 63 | .offset1{margin-left:80px;} 64 | .offset2{margin-left:140px;} 65 | .offset3{margin-left:200px;} 66 | .offset4{margin-left:260px;} 67 | .offset5{margin-left:320px;} 68 | .offset6{margin-left:380px;} 69 | .offset7{margin-left:440px;} 70 | .offset8{margin-left:500px;} 71 | .offset9{margin-left:560px;} 72 | .offset10{margin-left:620px;} 73 | .offset11{margin-left:680px;} 74 | .offset12{margin-left:740px;} 75 | .span-one-third{width:300px;} 76 | .span-two-thirds{width:620px;} 77 | .offset-one-third{margin-left:340px;} 78 | .offset-two-thirds{margin-left:660px;} 79 | p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} 80 | h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} 81 | h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} 82 | h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} 83 | h3,h4,h5,h6{line-height:36px;} 84 | h3{font-size:18px;}h3 small{font-size:14px;} 85 | h4{font-size:16px;}h4 small{font-size:12px;} 86 | h5{font-size:14px;} 87 | h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} 88 | ul,ol{margin:0 0 18px 25px;} 89 | ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} 90 | ul{list-style:disc;} 91 | ol{list-style:decimal;} 92 | li{line-height:18px;color:#808080;} 93 | ul.unstyled{list-style:none;margin-left:0;} 94 | dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} 95 | dl dt{font-weight:bold;} 96 | dl dd{margin-left:9px;} 97 | hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} 98 | strong{font-style:inherit;font-weight:bold;} 99 | em{font-style:italic;font-weight:inherit;line-height:inherit;} 100 | .muted{color:#bfbfbf;} 101 | blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} 102 | blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} 103 | address{display:block;line-height:18px;margin-bottom:18px;} 104 | code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 105 | code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} 106 | pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} 107 | form{margin-bottom:18px;} 108 | fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} 109 | form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;*display:inline;} 110 | form .clearfix:after{clear:both;} 111 | label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} 112 | label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} 113 | form .input{margin-left:150px;} 114 | input[type=checkbox],input[type=radio]{cursor:pointer;} 115 | input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 116 | input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} 117 | input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 118 | input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} 119 | select,input[type=file]{height:27px;line-height:27px;*margin-top:4px;} 120 | select[multiple]{height:inherit;} 121 | textarea{height:auto;} 122 | .uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} 123 | :-moz-placeholder{color:#bfbfbf;} 124 | ::-webkit-input-placeholder{color:#bfbfbf;} 125 | input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} 126 | input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} 127 | input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} 128 | form div.clearfix.error{background:#fae5e3;padding:10px 0;margin:-10px 0 10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}form div.clearfix.error>label,form div.clearfix.error span.help-inline,form div.clearfix.error span.help-block{color:#9d261d;} 129 | form div.clearfix.error input,form div.clearfix.error textarea{border-color:#c87872;-webkit-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);-moz-box-shadow:0 0 3px rgba(171, 41, 32, 0.25);box-shadow:0 0 3px rgba(171, 41, 32, 0.25);}form div.clearfix.error input:focus,form div.clearfix.error textarea:focus{border-color:#b9554d;-webkit-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);-moz-box-shadow:0 0 6px rgba(171, 41, 32, 0.5);box-shadow:0 0 6px rgba(171, 41, 32, 0.5);} 130 | form div.clearfix.error .input-prepend span.add-on,form div.clearfix.error .input-append span.add-on{background:#f4c8c5;border-color:#c87872;color:#b9554d;} 131 | .input-mini,input.mini,textarea.mini,select.mini{width:60px;} 132 | .input-small,input.small,textarea.small,select.small{width:90px;} 133 | .input-medium,input.medium,textarea.medium,select.medium{width:150px;} 134 | .input-large,input.large,textarea.large,select.large{width:210px;} 135 | .input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} 136 | .input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} 137 | textarea.xxlarge{overflow-y:auto;} 138 | input.span1,textarea.span1,select.span1{display:inline-block;float:none;width:30px;margin-left:0;} 139 | input.span2,textarea.span2,select.span2{display:inline-block;float:none;width:90px;margin-left:0;} 140 | input.span3,textarea.span3,select.span3{display:inline-block;float:none;width:150px;margin-left:0;} 141 | input.span4,textarea.span4,select.span4{display:inline-block;float:none;width:210px;margin-left:0;} 142 | input.span5,textarea.span5,select.span5{display:inline-block;float:none;width:270px;margin-left:0;} 143 | input.span6,textarea.span6,select.span6{display:inline-block;float:none;width:330px;margin-left:0;} 144 | input.span7,textarea.span7,select.span7{display:inline-block;float:none;width:390px;margin-left:0;} 145 | input.span8,textarea.span8,select.span8{display:inline-block;float:none;width:450px;margin-left:0;} 146 | input.span9,textarea.span9,select.span9{display:inline-block;float:none;width:510px;margin-left:0;} 147 | input.span10,textarea.span10,select.span10{display:inline-block;float:none;width:570px;margin-left:0;} 148 | input.span11,textarea.span11,select.span11{display:inline-block;float:none;width:630px;margin-left:0;} 149 | input.span12,textarea.span12,select.span12{display:inline-block;float:none;width:690px;margin-left:0;} 150 | input.span13,textarea.span13,select.span13{display:inline-block;float:none;width:750px;margin-left:0;} 151 | input.span14,textarea.span14,select.span14{display:inline-block;float:none;width:810px;margin-left:0;} 152 | input.span15,textarea.span15,select.span15{display:inline-block;float:none;width:870px;margin-left:0;} 153 | input.span16,textarea.span16,select.span16{display:inline-block;float:none;width:930px;margin-left:0;} 154 | input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} 155 | .actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} 156 | .help-inline,.help-block{font-size:11px;line-height:18px;color:#bfbfbf;} 157 | .help-inline{padding-left:5px;*position:relative;*top:-5px;} 158 | .help-block{display:block;max-width:600px;} 159 | .inline-inputs{color:#808080;}.inline-inputs span,.inline-inputs input{display:inline-block;} 160 | .inline-inputs input.mini{width:60px;} 161 | .inline-inputs input.small{width:90px;} 162 | .inline-inputs span{padding:0 2px 0 1px;} 163 | .input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} 164 | .input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 165 | .input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} 166 | .input-prepend .add-on{*margin-top:1px;} 167 | .input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 168 | .input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} 169 | .inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} 170 | .inputs-list label{display:block;float:none;width:auto;padding:0;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} 171 | .inputs-list label small{font-size:11px;font-weight:normal;} 172 | .inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} 173 | .inputs-list:first-child{padding-top:6px;} 174 | .inputs-list li+li{padding-top:2px;} 175 | .inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;} 176 | .form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} 177 | .form-stacked legend{padding-left:0;} 178 | .form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} 179 | .form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} 180 | .form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} 181 | .form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} 182 | .form-stacked .actions{margin-left:-20px;padding-left:20px;} 183 | table{width:100%;margin-bottom:18px;padding:0;border-collapse:separate;*border-collapse:collapse;font-size:13px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} 184 | table th{padding-top:9px;font-weight:bold;vertical-align:middle;border-bottom:1px solid #ddd;} 185 | table td{vertical-align:top;} 186 | table th+th,table td+td{border-left:1px solid #ddd;} 187 | table tr+tr td{border-top:1px solid #ddd;} 188 | table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} 189 | table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} 190 | table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} 191 | table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} 192 | .zebra-striped tbody tr:nth-child(odd) td{background-color:#f9f9f9;} 193 | .zebra-striped tbody tr:hover td{background-color:#f5f5f5;} 194 | table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} 195 | table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} 196 | table .header:hover:after{visibility:visible;} 197 | table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 198 | table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 199 | table .blue{color:#049cdb;border-bottom-color:#049cdb;} 200 | table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} 201 | table .green{color:#46a546;border-bottom-color:#46a546;} 202 | table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} 203 | table .red{color:#9d261d;border-bottom-color:#9d261d;} 204 | table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} 205 | table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} 206 | table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} 207 | table .orange{color:#f89406;border-bottom-color:#f89406;} 208 | table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} 209 | table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} 210 | table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} 211 | .topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} 212 | .topbar h3 a:hover,.topbar .brand a:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} 213 | .topbar h3{position:relative;} 214 | .topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} 215 | .topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} 216 | .topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} 217 | .topbar form.pull-right{float:right;} 218 | .topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} 219 | .topbar input::-webkit-input-placeholder{color:#e6e6e6;} 220 | .topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} 221 | .topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} 222 | .topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} 223 | .topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} 224 | .topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} 225 | .topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} 226 | .topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} 227 | .topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} 228 | .topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} 229 | .topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} 230 | .topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} 231 | .topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} 232 | .topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} 233 | li.menu,.dropdown{position:relative;} 234 | a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} 235 | .menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} 236 | .menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} 237 | .topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} 238 | .open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} 239 | .open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} 240 | .tabs,.pills{margin:0 0 20px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;*display:inline;} 241 | .tabs:after,.pills:after{clear:both;} 242 | .tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} 243 | .tabs{float:left;width:100%;border-bottom:1px solid #ddd;}.tabs>li{position:relative;top:1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:36px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} 244 | .tabs>li.active>a{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} 245 | .tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} 246 | .tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} 247 | .tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} 248 | .tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} 249 | .tab-content{clear:both;} 250 | .pills a{margin:5px 3px 5px 0;padding:0 15px;text-shadow:0 1px 1px #ffffff;line-height:30px;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{background:#00438a;color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} 251 | .pills .active a{background:#0069d6;color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);} 252 | .tab-content>*,.pill-content>*{display:none;} 253 | .tab-content>.active,.pill-content>.active{display:block;} 254 | .breadcrumb{margin:0 0 18px;padding:7px 14px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} 255 | .breadcrumb .divider{padding:0 5px;color:#bfbfbf;} 256 | .breadcrumb .active a{color:#404040;} 257 | .hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} 258 | .hero-unit p{font-size:18px;font-weight:200;line-height:27px;} 259 | footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} 260 | .page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} 261 | .btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} 262 | .btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 263 | .btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 264 | .btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 265 | .btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} 266 | .btn:focus{outline:1px dotted #666;} 267 | .btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 268 | .btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} 269 | .btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 270 | .btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 271 | .btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 272 | .btn.small{padding:7px 9px 7px;font-size:11px;} 273 | :root .alert-message,:root .btn{border-radius:0 \0;} 274 | button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} 275 | .close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);-khtml-opacity:0.2;-moz-opacity:0.2;opacity:0.2;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} 276 | .alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{*margin-top:3px;} 277 | .alert-message h5{line-height:18px;} 278 | .alert-message p{margin-bottom:0;} 279 | .alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} 280 | .alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} 281 | .alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} 282 | .alert-message.block-message ul{margin-bottom:0;} 283 | .alert-message.block-message li{color:#404040;} 284 | .alert-message.block-message .alert-actions{margin-top:5px;} 285 | .alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} 286 | .alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} 287 | .alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} 288 | .alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} 289 | .pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} 290 | .pagination li{display:inline;} 291 | .pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} 292 | .pagination a:hover,.pagination .active a{background-color:#c7eefe;} 293 | .pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} 294 | .pagination .next a{border:0;} 295 | .well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} 296 | .modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} 297 | .modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 298 | .modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -250px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} 299 | .modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} 300 | .modal.fade.in{top:50%;} 301 | .modal-header{border-bottom:1px solid #eee;padding:5px 15px;} 302 | .modal-body{padding:15px;} 303 | .modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;*display:inline;} 304 | .modal-footer:after{clear:both;} 305 | .modal-footer .btn{float:right;margin-left:5px;} 306 | .twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 307 | .twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 308 | .twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} 309 | .twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} 310 | .twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} 311 | .twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 312 | .twipsy-arrow{position:absolute;width:0;height:0;} 313 | .popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 314 | .popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} 315 | .popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} 316 | .popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} 317 | .popover .arrow{position:absolute;width:0;height:0;} 318 | .popover .inner{background-color:#000000;background-color:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} 319 | .popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} 320 | .popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} 321 | .fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} 322 | .label{padding:1px 3px 2px;background-color:#bfbfbf;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} 323 | .label.warning{background-color:#f89406;} 324 | .label.success{background-color:#46a546;} 325 | .label.notice{background-color:#62cffc;} 326 | .media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;*display:inline;} 327 | .media-grid:after{clear:both;} 328 | .media-grid li{display:inline;} 329 | .media-grid a{float:left;padding:4px;margin:0 0 20px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} 330 | .media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} 331 | -------------------------------------------------------------------------------- /lib/public/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | line-height: 1em; 3 | font-family: monospace; 4 | } 5 | 6 | .CodeMirror-scroll { 7 | overflow: auto; 8 | /* This is needed to prevent an IE[67] bug where the scrolled content 9 | is visible outside of the scrolling box. */ 10 | position: relative; 11 | outline: none; 12 | } 13 | 14 | .CodeMirror-gutter { 15 | position: absolute; 16 | left: 0; 17 | top: 0; 18 | z-index: 10; 19 | background-color: #f7f7f7; 20 | border-right: 1px solid #eee; 21 | min-width: 2em; 22 | height: 100%; 23 | } 24 | 25 | .CodeMirror-gutter-text { 26 | color: #aaa; 27 | text-align: right; 28 | padding: .4em .2em .4em .4em; 29 | white-space: pre !important; 30 | } 31 | 32 | .CodeMirror-lines { 33 | padding: .4em; 34 | white-space: pre; 35 | } 36 | 37 | .CodeMirror pre { 38 | -moz-border-radius: 0; 39 | -webkit-border-radius: 0; 40 | -o-border-radius: 0; 41 | border-radius: 0; 42 | border-width: 0; 43 | margin: 0; 44 | padding: 0; 45 | background: transparent; 46 | font-family: inherit; 47 | font-size: inherit; 48 | padding: 0; 49 | margin: 0; 50 | white-space: pre; 51 | word-wrap: normal; 52 | line-height: inherit; 53 | color: inherit; 54 | } 55 | 56 | .CodeMirror-wrap pre { 57 | word-wrap: break-word; 58 | white-space: pre-wrap; 59 | word-break: normal; 60 | } 61 | 62 | .CodeMirror-wrap .CodeMirror-scroll { 63 | overflow-x: hidden; 64 | } 65 | 66 | .CodeMirror textarea { 67 | outline: none !important; 68 | } 69 | 70 | .CodeMirror pre.CodeMirror-cursor { 71 | z-index: 10; 72 | position: absolute; 73 | visibility: hidden; 74 | border-left: 1px solid black; 75 | border-right: none; 76 | width: 0; 77 | } 78 | 79 | .cm-keymap-fat-cursor pre.CodeMirror-cursor { 80 | width: auto; 81 | border: 0; 82 | background: transparent; 83 | background: rgba(0, 200, 0, .4); 84 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = #6600c800, endColorstr = #4c00c800); 85 | } 86 | 87 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ 88 | .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { 89 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 90 | } 91 | 92 | .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite { 93 | } 94 | 95 | .CodeMirror-focused pre.CodeMirror-cursor { 96 | visibility: visible; 97 | } 98 | 99 | div.CodeMirror-selected { 100 | background: #d9d9d9; 101 | } 102 | 103 | .CodeMirror-focused div.CodeMirror-selected { 104 | background: #d7d4f0; 105 | } 106 | 107 | .CodeMirror-searching { 108 | background: #ffa; 109 | background: rgba(255, 255, 0, .4); 110 | } 111 | 112 | /* Default theme */ 113 | 114 | .cm-s-default span.cm-keyword { 115 | color: #708; 116 | } 117 | 118 | .cm-s-default span.cm-atom { 119 | color: #219; 120 | } 121 | 122 | .cm-s-default span.cm-number { 123 | color: #164; 124 | } 125 | 126 | .cm-s-default span.cm-def { 127 | color: #00f; 128 | } 129 | 130 | .cm-s-default span.cm-variable { 131 | color: black; 132 | } 133 | 134 | .cm-s-default span.cm-variable-2 { 135 | color: #05a; 136 | } 137 | 138 | .cm-s-default span.cm-variable-3 { 139 | color: #085; 140 | } 141 | 142 | .cm-s-default span.cm-property { 143 | color: black; 144 | } 145 | 146 | .cm-s-default span.cm-operator { 147 | color: black; 148 | } 149 | 150 | .cm-s-default span.cm-comment { 151 | color: #a50; 152 | } 153 | 154 | .cm-s-default span.cm-string { 155 | color: #a11; 156 | } 157 | 158 | .cm-s-default span.cm-string-2 { 159 | color: #f50; 160 | } 161 | 162 | .cm-s-default span.cm-meta { 163 | color: #555; 164 | } 165 | 166 | .cm-s-default span.cm-error { 167 | color: #f00; 168 | } 169 | 170 | .cm-s-default span.cm-qualifier { 171 | color: #555; 172 | } 173 | 174 | .cm-s-default span.cm-builtin { 175 | color: #30a; 176 | } 177 | 178 | .cm-s-default span.cm-bracket { 179 | color: #cc7; 180 | } 181 | 182 | .cm-s-default span.cm-tag { 183 | color: #170; 184 | } 185 | 186 | .cm-s-default span.cm-attribute { 187 | color: #00c; 188 | } 189 | 190 | .cm-s-default span.cm-header { 191 | color: #a0a; 192 | } 193 | 194 | .cm-s-default span.cm-quote { 195 | color: #090; 196 | } 197 | 198 | .cm-s-default span.cm-hr { 199 | color: #999; 200 | } 201 | 202 | .cm-s-default span.cm-link { 203 | color: #00c; 204 | } 205 | 206 | span.cm-header, span.cm-strong { 207 | font-weight: bold; 208 | } 209 | 210 | span.cm-em { 211 | font-style: italic; 212 | } 213 | 214 | span.cm-emstrong { 215 | font-style: italic; 216 | font-weight: bold; 217 | } 218 | 219 | span.cm-link { 220 | text-decoration: underline; 221 | } 222 | 223 | div.CodeMirror span.CodeMirror-matchingbracket { 224 | color: #0f0; 225 | } 226 | 227 | div.CodeMirror span.CodeMirror-nonmatchingbracket { 228 | color: #f22; 229 | } 230 | -------------------------------------------------------------------------------- /lib/public/js/example.js: -------------------------------------------------------------------------------- 1 | function mirror(textarea, contentType, options) { 2 | $textarea = $(textarea); 3 | if ($textarea.val() != '') { 4 | if(contentType.indexOf('json') >= 0) { 5 | $textarea.val(JSON.stringify(JSON.parse($textarea.val()), undefined, 2)); 6 | options.json = true; 7 | options.mode = 'javascript'; 8 | } else if (contentType.indexOf('javascript') >= 0) { 9 | options.mode = 'javascript'; 10 | } else if (contentType.indexOf('xml') >= 0) { 11 | options.mode = 'xml'; 12 | } else { 13 | options.mode = 'htmlmixed'; 14 | } 15 | } 16 | return CodeMirror.fromTextArea(textarea, options); 17 | }; 18 | 19 | $(function(){ 20 | $(".request .body .content").each(function(i, el) { 21 | el = $(el); 22 | mirror(el.find("textarea")[0], el.data("content-type"), { "readOnly": true, "lineNumbers": true }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/public/js/mode/css/css.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("css", function(config) { 2 | var indentUnit = config.indentUnit, type; 3 | function ret(style, tp) {type = tp; return style;} 4 | 5 | function tokenBase(stream, state) { 6 | var ch = stream.next(); 7 | if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} 8 | else if (ch == "/" && stream.eat("*")) { 9 | state.tokenize = tokenCComment; 10 | return tokenCComment(stream, state); 11 | } 12 | else if (ch == "<" && stream.eat("!")) { 13 | state.tokenize = tokenSGMLComment; 14 | return tokenSGMLComment(stream, state); 15 | } 16 | else if (ch == "=") ret(null, "compare"); 17 | else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); 18 | else if (ch == "\"" || ch == "'") { 19 | state.tokenize = tokenString(ch); 20 | return state.tokenize(stream, state); 21 | } 22 | else if (ch == "#") { 23 | stream.eatWhile(/[\w\\\-]/); 24 | return ret("atom", "hash"); 25 | } 26 | else if (ch == "!") { 27 | stream.match(/^\s*\w*/); 28 | return ret("keyword", "important"); 29 | } 30 | else if (/\d/.test(ch)) { 31 | stream.eatWhile(/[\w.%]/); 32 | return ret("number", "unit"); 33 | } 34 | else if (/[,.+>*\/]/.test(ch)) { 35 | return ret(null, "select-op"); 36 | } 37 | else if (/[;{}:\[\]]/.test(ch)) { 38 | return ret(null, ch); 39 | } 40 | else { 41 | stream.eatWhile(/[\w\\\-]/); 42 | return ret("variable", "variable"); 43 | } 44 | } 45 | 46 | function tokenCComment(stream, state) { 47 | var maybeEnd = false, ch; 48 | while ((ch = stream.next()) != null) { 49 | if (maybeEnd && ch == "/") { 50 | state.tokenize = tokenBase; 51 | break; 52 | } 53 | maybeEnd = (ch == "*"); 54 | } 55 | return ret("comment", "comment"); 56 | } 57 | 58 | function tokenSGMLComment(stream, state) { 59 | var dashes = 0, ch; 60 | while ((ch = stream.next()) != null) { 61 | if (dashes >= 2 && ch == ">") { 62 | state.tokenize = tokenBase; 63 | break; 64 | } 65 | dashes = (ch == "-") ? dashes + 1 : 0; 66 | } 67 | return ret("comment", "comment"); 68 | } 69 | 70 | function tokenString(quote) { 71 | return function(stream, state) { 72 | var escaped = false, ch; 73 | while ((ch = stream.next()) != null) { 74 | if (ch == quote && !escaped) 75 | break; 76 | escaped = !escaped && ch == "\\"; 77 | } 78 | if (!escaped) state.tokenize = tokenBase; 79 | return ret("string", "string"); 80 | }; 81 | } 82 | 83 | return { 84 | startState: function(base) { 85 | return {tokenize: tokenBase, 86 | baseIndent: base || 0, 87 | stack: []}; 88 | }, 89 | 90 | token: function(stream, state) { 91 | if (stream.eatSpace()) return null; 92 | var style = state.tokenize(stream, state); 93 | 94 | var context = state.stack[state.stack.length-1]; 95 | if (type == "hash" && context != "rule") style = "string-2"; 96 | else if (style == "variable") { 97 | if (context == "rule") style = "number"; 98 | else if (!context || context == "@media{") style = "tag"; 99 | } 100 | 101 | if (context == "rule" && /^[\{\};]$/.test(type)) 102 | state.stack.pop(); 103 | if (type == "{") { 104 | if (context == "@media") state.stack[state.stack.length-1] = "@media{"; 105 | else state.stack.push("{"); 106 | } 107 | else if (type == "}") state.stack.pop(); 108 | else if (type == "@media") state.stack.push("@media"); 109 | else if (context == "{" && type != "comment") state.stack.push("rule"); 110 | return style; 111 | }, 112 | 113 | indent: function(state, textAfter) { 114 | var n = state.stack.length; 115 | if (/^\}/.test(textAfter)) 116 | n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; 117 | return state.baseIndent + n * indentUnit; 118 | }, 119 | 120 | electricChars: "}" 121 | }; 122 | }); 123 | 124 | CodeMirror.defineMIME("text/css", "css"); 125 | -------------------------------------------------------------------------------- /lib/public/js/mode/htmlmixed/htmlmixed.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { 2 | var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); 3 | var jsMode = CodeMirror.getMode(config, "javascript"); 4 | var cssMode = CodeMirror.getMode(config, "css"); 5 | 6 | function html(stream, state) { 7 | var style = htmlMode.token(stream, state.htmlState); 8 | if (style == "tag" && stream.current() == ">" && state.htmlState.context) { 9 | if (/^script$/i.test(state.htmlState.context.tagName)) { 10 | state.token = javascript; 11 | state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); 12 | state.mode = "javascript"; 13 | } 14 | else if (/^style$/i.test(state.htmlState.context.tagName)) { 15 | state.token = css; 16 | state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); 17 | state.mode = "css"; 18 | } 19 | } 20 | return style; 21 | } 22 | function maybeBackup(stream, pat, style) { 23 | var cur = stream.current(); 24 | var close = cur.search(pat); 25 | if (close > -1) stream.backUp(cur.length - close); 26 | return style; 27 | } 28 | function javascript(stream, state) { 29 | if (stream.match(/^<\/\s*script\s*>/i, false)) { 30 | state.token = html; 31 | state.localState = null; 32 | state.mode = "html"; 33 | return html(stream, state); 34 | } 35 | return maybeBackup(stream, /<\/\s*script\s*>/, 36 | jsMode.token(stream, state.localState)); 37 | } 38 | function css(stream, state) { 39 | if (stream.match(/^<\/\s*style\s*>/i, false)) { 40 | state.token = html; 41 | state.localState = null; 42 | state.mode = "html"; 43 | return html(stream, state); 44 | } 45 | return maybeBackup(stream, /<\/\s*style\s*>/, 46 | cssMode.token(stream, state.localState)); 47 | } 48 | 49 | return { 50 | startState: function() { 51 | var state = htmlMode.startState(); 52 | return {token: html, localState: null, mode: "html", htmlState: state}; 53 | }, 54 | 55 | copyState: function(state) { 56 | if (state.localState) 57 | var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState); 58 | return {token: state.token, localState: local, mode: state.mode, 59 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 60 | }, 61 | 62 | token: function(stream, state) { 63 | return state.token(stream, state); 64 | }, 65 | 66 | indent: function(state, textAfter) { 67 | if (state.token == html || /^\s*<\//.test(textAfter)) 68 | return htmlMode.indent(state.htmlState, textAfter); 69 | else if (state.token == javascript) 70 | return jsMode.indent(state.localState, textAfter); 71 | else 72 | return cssMode.indent(state.localState, textAfter); 73 | }, 74 | 75 | compareStates: function(a, b) { 76 | if (a.mode != b.mode) return false; 77 | if (a.localState) return CodeMirror.Pass; 78 | return htmlMode.compareStates(a.htmlState, b.htmlState); 79 | }, 80 | 81 | electricChars: "/{}:" 82 | } 83 | }, "xml", "javascript", "css"); 84 | 85 | CodeMirror.defineMIME("text/html", "htmlmixed"); 86 | -------------------------------------------------------------------------------- /lib/public/js/mode/javascript/javascript.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit; 3 | var jsonMode = parserConfig.json; 4 | 5 | // Tokenizer 6 | 7 | var keywords = function(){ 8 | function kw(type) {return {type: type, style: "keyword"};} 9 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 10 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 11 | return { 12 | "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 13 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, 14 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 15 | "function": kw("function"), "catch": kw("catch"), 16 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 17 | "in": operator, "typeof": operator, "instanceof": operator, 18 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 19 | }; 20 | }(); 21 | 22 | var isOperatorChar = /[+\-*&%=<>!?|]/; 23 | 24 | function chain(stream, state, f) { 25 | state.tokenize = f; 26 | return f(stream, state); 27 | } 28 | 29 | function nextUntilUnescaped(stream, end) { 30 | var escaped = false, next; 31 | while ((next = stream.next()) != null) { 32 | if (next == end && !escaped) 33 | return false; 34 | escaped = !escaped && next == "\\"; 35 | } 36 | return escaped; 37 | } 38 | 39 | // Used as scratch variables to communicate multiple values without 40 | // consing up tons of objects. 41 | var type, content; 42 | function ret(tp, style, cont) { 43 | type = tp; content = cont; 44 | return style; 45 | } 46 | 47 | function jsTokenBase(stream, state) { 48 | var ch = stream.next(); 49 | if (ch == '"' || ch == "'") 50 | return chain(stream, state, jsTokenString(ch)); 51 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 52 | return ret(ch); 53 | else if (ch == "0" && stream.eat(/x/i)) { 54 | stream.eatWhile(/[\da-f]/i); 55 | return ret("number", "number"); 56 | } 57 | else if (/\d/.test(ch)) { 58 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 59 | return ret("number", "number"); 60 | } 61 | else if (ch == "/") { 62 | if (stream.eat("*")) { 63 | return chain(stream, state, jsTokenComment); 64 | } 65 | else if (stream.eat("/")) { 66 | stream.skipToEnd(); 67 | return ret("comment", "comment"); 68 | } 69 | else if (state.reAllowed) { 70 | nextUntilUnescaped(stream, "/"); 71 | stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla 72 | return ret("regexp", "string-2"); 73 | } 74 | else { 75 | stream.eatWhile(isOperatorChar); 76 | return ret("operator", null, stream.current()); 77 | } 78 | } 79 | else if (ch == "#") { 80 | stream.skipToEnd(); 81 | return ret("error", "error"); 82 | } 83 | else if (isOperatorChar.test(ch)) { 84 | stream.eatWhile(isOperatorChar); 85 | return ret("operator", null, stream.current()); 86 | } 87 | else { 88 | stream.eatWhile(/[\w\$_]/); 89 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 90 | return (known && state.kwAllowed) ? ret(known.type, known.style, word) : 91 | ret("variable", "variable", word); 92 | } 93 | } 94 | 95 | function jsTokenString(quote) { 96 | return function(stream, state) { 97 | if (!nextUntilUnescaped(stream, quote)) 98 | state.tokenize = jsTokenBase; 99 | return ret("string", "string"); 100 | }; 101 | } 102 | 103 | function jsTokenComment(stream, state) { 104 | var maybeEnd = false, ch; 105 | while (ch = stream.next()) { 106 | if (ch == "/" && maybeEnd) { 107 | state.tokenize = jsTokenBase; 108 | break; 109 | } 110 | maybeEnd = (ch == "*"); 111 | } 112 | return ret("comment", "comment"); 113 | } 114 | 115 | // Parser 116 | 117 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 118 | 119 | function JSLexical(indented, column, type, align, prev, info) { 120 | this.indented = indented; 121 | this.column = column; 122 | this.type = type; 123 | this.prev = prev; 124 | this.info = info; 125 | if (align != null) this.align = align; 126 | } 127 | 128 | function inScope(state, varname) { 129 | for (var v = state.localVars; v; v = v.next) 130 | if (v.name == varname) return true; 131 | } 132 | 133 | function parseJS(state, style, type, content, stream) { 134 | var cc = state.cc; 135 | // Communicate our context to the combinators. 136 | // (Less wasteful than consing up a hundred closures on every call.) 137 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; 138 | 139 | if (!state.lexical.hasOwnProperty("align")) 140 | state.lexical.align = true; 141 | 142 | while(true) { 143 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 144 | if (combinator(type, content)) { 145 | while(cc.length && cc[cc.length - 1].lex) 146 | cc.pop()(); 147 | if (cx.marked) return cx.marked; 148 | if (type == "variable" && inScope(state, content)) return "variable-2"; 149 | return style; 150 | } 151 | } 152 | } 153 | 154 | // Combinator utils 155 | 156 | var cx = {state: null, column: null, marked: null, cc: null}; 157 | function pass() { 158 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 159 | } 160 | function cont() { 161 | pass.apply(null, arguments); 162 | return true; 163 | } 164 | function register(varname) { 165 | var state = cx.state; 166 | if (state.context) { 167 | cx.marked = "def"; 168 | for (var v = state.localVars; v; v = v.next) 169 | if (v.name == varname) return; 170 | state.localVars = {name: varname, next: state.localVars}; 171 | } 172 | } 173 | 174 | // Combinators 175 | 176 | var defaultVars = {name: "this", next: {name: "arguments"}}; 177 | function pushcontext() { 178 | if (!cx.state.context) cx.state.localVars = defaultVars; 179 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 180 | } 181 | function popcontext() { 182 | cx.state.localVars = cx.state.context.vars; 183 | cx.state.context = cx.state.context.prev; 184 | } 185 | function pushlex(type, info) { 186 | var result = function() { 187 | var state = cx.state; 188 | state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info) 189 | }; 190 | result.lex = true; 191 | return result; 192 | } 193 | function poplex() { 194 | var state = cx.state; 195 | if (state.lexical.prev) { 196 | if (state.lexical.type == ")") 197 | state.indented = state.lexical.indented; 198 | state.lexical = state.lexical.prev; 199 | } 200 | } 201 | poplex.lex = true; 202 | 203 | function expect(wanted) { 204 | return function expecting(type) { 205 | if (type == wanted) return cont(); 206 | else if (wanted == ";") return pass(); 207 | else return cont(arguments.callee); 208 | }; 209 | } 210 | 211 | function statement(type) { 212 | if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); 213 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 214 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 215 | if (type == "{") return cont(pushlex("}"), block, poplex); 216 | if (type == ";") return cont(); 217 | if (type == "function") return cont(functiondef); 218 | if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), 219 | poplex, statement, poplex); 220 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 221 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 222 | block, poplex, poplex); 223 | if (type == "case") return cont(expression, expect(":")); 224 | if (type == "default") return cont(expect(":")); 225 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 226 | statement, poplex, popcontext); 227 | return pass(pushlex("stat"), expression, expect(";"), poplex); 228 | } 229 | function expression(type) { 230 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); 231 | if (type == "function") return cont(functiondef); 232 | if (type == "keyword c") return cont(maybeexpression); 233 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); 234 | if (type == "operator") return cont(expression); 235 | if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 236 | if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 237 | return cont(); 238 | } 239 | function maybeexpression(type) { 240 | if (type.match(/[;\}\)\],]/)) return pass(); 241 | return pass(expression); 242 | } 243 | 244 | function maybeoperator(type, value) { 245 | if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); 246 | if (type == "operator" || type == ":") return cont(expression); 247 | if (type == ";") return; 248 | if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 249 | if (type == ".") return cont(property, maybeoperator); 250 | if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 251 | } 252 | function maybelabel(type) { 253 | if (type == ":") return cont(poplex, statement); 254 | return pass(maybeoperator, expect(";"), poplex); 255 | } 256 | function property(type) { 257 | if (type == "variable") {cx.marked = "property"; return cont();} 258 | } 259 | function objprop(type) { 260 | if (type == "variable") cx.marked = "property"; 261 | if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); 262 | } 263 | function commasep(what, end) { 264 | function proceed(type) { 265 | if (type == ",") return cont(what, proceed); 266 | if (type == end) return cont(); 267 | return cont(expect(end)); 268 | } 269 | return function commaSeparated(type) { 270 | if (type == end) return cont(); 271 | else return pass(what, proceed); 272 | }; 273 | } 274 | function block(type) { 275 | if (type == "}") return cont(); 276 | return pass(statement, block); 277 | } 278 | function vardef1(type, value) { 279 | if (type == "variable"){register(value); return cont(vardef2);} 280 | return cont(); 281 | } 282 | function vardef2(type, value) { 283 | if (value == "=") return cont(expression, vardef2); 284 | if (type == ",") return cont(vardef1); 285 | } 286 | function forspec1(type) { 287 | if (type == "var") return cont(vardef1, forspec2); 288 | if (type == ";") return pass(forspec2); 289 | if (type == "variable") return cont(formaybein); 290 | return pass(forspec2); 291 | } 292 | function formaybein(type, value) { 293 | if (value == "in") return cont(expression); 294 | return cont(maybeoperator, forspec2); 295 | } 296 | function forspec2(type, value) { 297 | if (type == ";") return cont(forspec3); 298 | if (value == "in") return cont(expression); 299 | return cont(expression, expect(";"), forspec3); 300 | } 301 | function forspec3(type) { 302 | if (type != ")") cont(expression); 303 | } 304 | function functiondef(type, value) { 305 | if (type == "variable") {register(value); return cont(functiondef);} 306 | if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 307 | } 308 | function funarg(type, value) { 309 | if (type == "variable") {register(value); return cont();} 310 | } 311 | 312 | // Interface 313 | 314 | return { 315 | startState: function(basecolumn) { 316 | return { 317 | tokenize: jsTokenBase, 318 | reAllowed: true, 319 | kwAllowed: true, 320 | cc: [], 321 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 322 | localVars: parserConfig.localVars, 323 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 324 | indented: 0 325 | }; 326 | }, 327 | 328 | token: function(stream, state) { 329 | if (stream.sol()) { 330 | if (!state.lexical.hasOwnProperty("align")) 331 | state.lexical.align = false; 332 | state.indented = stream.indentation(); 333 | } 334 | if (stream.eatSpace()) return null; 335 | var style = state.tokenize(stream, state); 336 | if (type == "comment") return style; 337 | state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); 338 | state.kwAllowed = type != '.'; 339 | return parseJS(state, style, type, content, stream); 340 | }, 341 | 342 | indent: function(state, textAfter) { 343 | if (state.tokenize != jsTokenBase) return 0; 344 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 345 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 346 | var type = lexical.type, closing = firstChar == type; 347 | if (type == "vardef") return lexical.indented + 4; 348 | else if (type == "form" && firstChar == "{") return lexical.indented; 349 | else if (type == "stat" || type == "form") return lexical.indented + indentUnit; 350 | else if (lexical.info == "switch" && !closing) 351 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 352 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 353 | else return lexical.indented + (closing ? 0 : indentUnit); 354 | }, 355 | 356 | electricChars: ":{}" 357 | }; 358 | }); 359 | 360 | CodeMirror.defineMIME("text/javascript", "javascript"); 361 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 362 | -------------------------------------------------------------------------------- /lib/public/js/mode/xml/xml.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("xml", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit; 3 | var Kludges = parserConfig.htmlMode ? { 4 | autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 5 | 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 6 | 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 7 | 'track': true, 'wbr': true}, 8 | implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 9 | 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 10 | 'th': true, 'tr': true}, 11 | contextGrabbers: { 12 | 'dd': {'dd': true, 'dt': true}, 13 | 'dt': {'dd': true, 'dt': true}, 14 | 'li': {'li': true}, 15 | 'option': {'option': true, 'optgroup': true}, 16 | 'optgroup': {'optgroup': true}, 17 | 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 18 | 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 19 | 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 20 | 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 21 | 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 22 | 'rp': {'rp': true, 'rt': true}, 23 | 'rt': {'rp': true, 'rt': true}, 24 | 'tbody': {'tbody': true, 'tfoot': true}, 25 | 'td': {'td': true, 'th': true}, 26 | 'tfoot': {'tbody': true}, 27 | 'th': {'td': true, 'th': true}, 28 | 'thead': {'tbody': true, 'tfoot': true}, 29 | 'tr': {'tr': true} 30 | }, 31 | doNotIndent: {"pre": true}, 32 | allowUnquoted: true, 33 | allowMissing: false 34 | } : { 35 | autoSelfClosers: {}, 36 | implicitlyClosed: {}, 37 | contextGrabbers: {}, 38 | doNotIndent: {}, 39 | allowUnquoted: false, 40 | allowMissing: false 41 | }; 42 | var alignCDATA = parserConfig.alignCDATA; 43 | 44 | // Return variables for tokenizers 45 | var tagName, type; 46 | 47 | function inText(stream, state) { 48 | function chain(parser) { 49 | state.tokenize = parser; 50 | return parser(stream, state); 51 | } 52 | 53 | var ch = stream.next(); 54 | if (ch == "<") { 55 | if (stream.eat("!")) { 56 | if (stream.eat("[")) { 57 | if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 58 | else return null; 59 | } 60 | else if (stream.match("--")) return chain(inBlock("comment", "-->")); 61 | else if (stream.match("DOCTYPE", true, true)) { 62 | stream.eatWhile(/[\w\._\-]/); 63 | return chain(doctype(1)); 64 | } 65 | else return null; 66 | } 67 | else if (stream.eat("?")) { 68 | stream.eatWhile(/[\w\._\-]/); 69 | state.tokenize = inBlock("meta", "?>"); 70 | return "meta"; 71 | } 72 | else { 73 | type = stream.eat("/") ? "closeTag" : "openTag"; 74 | stream.eatSpace(); 75 | tagName = ""; 76 | var c; 77 | while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; 78 | state.tokenize = inTag; 79 | return "tag"; 80 | } 81 | } 82 | else if (ch == "&") { 83 | var ok; 84 | if (stream.eat("#")) { 85 | if (stream.eat("x")) { 86 | ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 87 | } else { 88 | ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 89 | } 90 | } else { 91 | ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 92 | } 93 | return ok ? "atom" : "error"; 94 | } 95 | else { 96 | stream.eatWhile(/[^&<]/); 97 | return null; 98 | } 99 | } 100 | 101 | function inTag(stream, state) { 102 | var ch = stream.next(); 103 | if (ch == ">" || (ch == "/" && stream.eat(">"))) { 104 | state.tokenize = inText; 105 | type = ch == ">" ? "endTag" : "selfcloseTag"; 106 | return "tag"; 107 | } 108 | else if (ch == "=") { 109 | type = "equals"; 110 | return null; 111 | } 112 | else if (/[\'\"]/.test(ch)) { 113 | state.tokenize = inAttribute(ch); 114 | return state.tokenize(stream, state); 115 | } 116 | else { 117 | stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); 118 | return "word"; 119 | } 120 | } 121 | 122 | function inAttribute(quote) { 123 | return function(stream, state) { 124 | while (!stream.eol()) { 125 | if (stream.next() == quote) { 126 | state.tokenize = inTag; 127 | break; 128 | } 129 | } 130 | return "string"; 131 | }; 132 | } 133 | 134 | function inBlock(style, terminator) { 135 | return function(stream, state) { 136 | while (!stream.eol()) { 137 | if (stream.match(terminator)) { 138 | state.tokenize = inText; 139 | break; 140 | } 141 | stream.next(); 142 | } 143 | return style; 144 | }; 145 | } 146 | function doctype(depth) { 147 | return function(stream, state) { 148 | var ch; 149 | while ((ch = stream.next()) != null) { 150 | if (ch == "<") { 151 | state.tokenize = doctype(depth + 1); 152 | return state.tokenize(stream, state); 153 | } else if (ch == ">") { 154 | if (depth == 1) { 155 | state.tokenize = inText; 156 | break; 157 | } else { 158 | state.tokenize = doctype(depth - 1); 159 | return state.tokenize(stream, state); 160 | } 161 | } 162 | } 163 | return "meta"; 164 | }; 165 | } 166 | 167 | var curState, setStyle; 168 | function pass() { 169 | for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); 170 | } 171 | function cont() { 172 | pass.apply(null, arguments); 173 | return true; 174 | } 175 | 176 | function pushContext(tagName, startOfLine) { 177 | var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); 178 | curState.context = { 179 | prev: curState.context, 180 | tagName: tagName, 181 | indent: curState.indented, 182 | startOfLine: startOfLine, 183 | noIndent: noIndent 184 | }; 185 | } 186 | function popContext() { 187 | if (curState.context) curState.context = curState.context.prev; 188 | } 189 | 190 | function element(type) { 191 | if (type == "openTag") { 192 | curState.tagName = tagName; 193 | return cont(attributes, endtag(curState.startOfLine)); 194 | } else if (type == "closeTag") { 195 | var err = false; 196 | if (curState.context) { 197 | if (curState.context.tagName != tagName) { 198 | if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { 199 | popContext(); 200 | } 201 | err = !curState.context || curState.context.tagName != tagName; 202 | } 203 | } else { 204 | err = true; 205 | } 206 | if (err) setStyle = "error"; 207 | return cont(endclosetag(err)); 208 | } 209 | return cont(); 210 | } 211 | function endtag(startOfLine) { 212 | return function(type) { 213 | if (type == "selfcloseTag" || 214 | (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) { 215 | maybePopContext(curState.tagName.toLowerCase()); 216 | return cont(); 217 | } 218 | if (type == "endTag") { 219 | maybePopContext(curState.tagName.toLowerCase()); 220 | pushContext(curState.tagName, startOfLine); 221 | return cont(); 222 | } 223 | return cont(); 224 | }; 225 | } 226 | function endclosetag(err) { 227 | return function(type) { 228 | if (err) setStyle = "error"; 229 | if (type == "endTag") { popContext(); return cont(); } 230 | setStyle = "error"; 231 | return cont(arguments.callee); 232 | } 233 | } 234 | function maybePopContext(nextTagName) { 235 | var parentTagName; 236 | while (true) { 237 | if (!curState.context) { 238 | return; 239 | } 240 | parentTagName = curState.context.tagName.toLowerCase(); 241 | if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || 242 | !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 243 | return; 244 | } 245 | popContext(); 246 | } 247 | } 248 | 249 | function attributes(type) { 250 | if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} 251 | if (type == "endTag" || type == "selfcloseTag") return pass(); 252 | setStyle = "error"; 253 | return cont(attributes); 254 | } 255 | function attribute(type) { 256 | if (type == "equals") return cont(attvalue, attributes); 257 | if (!Kludges.allowMissing) setStyle = "error"; 258 | return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); 259 | } 260 | function attvalue(type) { 261 | if (type == "string") return cont(attvaluemaybe); 262 | if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} 263 | setStyle = "error"; 264 | return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); 265 | } 266 | function attvaluemaybe(type) { 267 | if (type == "string") return cont(attvaluemaybe); 268 | else return pass(); 269 | } 270 | 271 | return { 272 | startState: function() { 273 | return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; 274 | }, 275 | 276 | token: function(stream, state) { 277 | if (stream.sol()) { 278 | state.startOfLine = true; 279 | state.indented = stream.indentation(); 280 | } 281 | if (stream.eatSpace()) return null; 282 | 283 | setStyle = type = tagName = null; 284 | var style = state.tokenize(stream, state); 285 | state.type = type; 286 | if ((style || type) && style != "comment") { 287 | curState = state; 288 | while (true) { 289 | var comb = state.cc.pop() || element; 290 | if (comb(type || style)) break; 291 | } 292 | } 293 | state.startOfLine = false; 294 | return setStyle || style; 295 | }, 296 | 297 | indent: function(state, textAfter, fullLine) { 298 | var context = state.context; 299 | if ((state.tokenize != inTag && state.tokenize != inText) || 300 | context && context.noIndent) 301 | return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 302 | if (alignCDATA && / :html5 22 | set :root, File.join(File.dirname(__FILE__), "..") 23 | 24 | # Main index, displays all examples grouped by resource 25 | get "/" do 26 | index = Index.new(File.join(docs_dir, "index.json")) 27 | haml :index, :locals => { :index => index } 28 | end 29 | 30 | # Allows for overriding styles 31 | get "/custom-css/*" do 32 | file = "#{docs_dir}/styles/#{params[:splat][0]}" 33 | 34 | if !File.exist?(file) 35 | raise Sinatra::NotFound 36 | end 37 | 38 | content_type :css 39 | File.read(file) 40 | end 41 | 42 | if markdown 43 | get "/guides/*" do 44 | file = "#{guides_dir}/#{params[:splat][0]}.md" 45 | erb :guide, locals: { guide: File.read(file) }, layout: false 46 | end 47 | end 48 | 49 | # Catch all for example pages. 50 | # Loads files from the docs dir and appends '.json'. 51 | # 52 | # @example 53 | # "/orders/create_an_order" => "docs/api/orders/create_an_order.json" 54 | get "/*" do 55 | file = "#{docs_dir}/#{params[:splat][0]}.json" 56 | 57 | if !File.exist?(file) 58 | raise Sinatra::NotFound 59 | end 60 | 61 | index = Index.new(File.join(docs_dir, "index.json")) 62 | example = Example.new(file) 63 | 64 | haml :example, :locals => { index: index, example: example } 65 | end 66 | 67 | # Page not found 68 | not_found do 69 | "Example does not exist" 70 | end 71 | 72 | helpers do 73 | def link_to(name, link) 74 | %{#{name}} 75 | end 76 | 77 | def url_location 78 | "#{url_prefix}#{request.env["SCRIPT_NAME"]}" 79 | end 80 | 81 | def url_prefix 82 | url = Raddocs.configuration.url_prefix 83 | return '' if url.to_s.empty? 84 | url.start_with?('/') ? url : "/#{url}" 85 | end 86 | 87 | def api_name 88 | Raddocs.configuration.api_name 89 | end 90 | 91 | # Loads all necessary css files 92 | # 93 | # @see Raddocs::Configuration for loading external files 94 | def css_files 95 | files = ["#{url_location}/codemirror.css", "#{url_location}/application.css"] 96 | 97 | if Raddocs.configuration.include_bootstrap 98 | files << "#{url_location}/bootstrap.min.css" 99 | end 100 | 101 | Dir.glob(File.join(docs_dir, "styles", "*.css")).each do |css_file| 102 | basename = Pathname.new(css_file).basename 103 | files << "#{url_location}/custom-css/#{basename}" 104 | end 105 | 106 | files.concat Array(Raddocs.configuration.external_css) 107 | 108 | files 109 | end 110 | 111 | def docs_dir 112 | Raddocs.configuration.docs_dir 113 | end 114 | 115 | def guides 116 | return [] unless File.exist?(guides_index) 117 | YAML.load(File.read(guides_index)).map do |guide_hash| 118 | Guide.new(guide_hash) 119 | end 120 | end 121 | 122 | def guides_index 123 | File.join guides_dir, "guides.yml" 124 | end 125 | 126 | def guides_dir 127 | Raddocs.configuration.guides_dir 128 | end 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /lib/raddocs/configuration.rb: -------------------------------------------------------------------------------- 1 | module Raddocs 2 | # Configure Raddocs 3 | # @see Raddocs.configure Raddocs.configure 4 | # @example 5 | # Raddocs.configure do |config| 6 | # # config is this class 7 | # config.api_name = "My API" 8 | # end 9 | class Configuration 10 | # Configures a new setting, creates two methods 11 | # 12 | # @param name [Symbol] name of the setting 13 | # @param opts [Hash] 14 | # @option opts [Object] default default value of setting 15 | def self.add_setting(name, opts = {}) 16 | define_method("#{name}=") { |value| settings[name] = value } 17 | define_method("#{name}") do 18 | if settings.has_key?(name) 19 | settings[name] 20 | elsif opts[:default].respond_to?(:call) 21 | opts[:default].call(self) 22 | else 23 | opts[:default] 24 | end 25 | end 26 | end 27 | 28 | # @!attribute docs_dir 29 | # @return [String] defaults to 'doc/api' 30 | add_setting :docs_dir, :default => "doc/api" 31 | 32 | # @!attribute guides_dir 33 | # @return [String] defaults to 'doc/guides' 34 | add_setting :guides_dir, :default => "doc/guides" 35 | 36 | # @!attribute docs_mime_type 37 | # @return [Regexp] defaults to Regexp.new("text/docs\+plain") 38 | add_setting :docs_mime_type, :default => /text\/docs\+plain/ 39 | 40 | # @!attribute api_name 41 | # @return [String] defaults to "Api Documentation" 42 | add_setting :api_name, :default => "Api Documentation" 43 | 44 | # @!attribute include_bootstrap 45 | # @return [Boolean] defaults to true 46 | add_setting :include_bootstrap, :default => true 47 | 48 | # @!attribute external_css 49 | # @return [Array] array of Strings, defaults to [] 50 | add_setting :external_css, :default => [] 51 | 52 | # @!attribute url_prefix 53 | # @return [String] defaults to nil 54 | add_setting :url_prefix, :default => nil 55 | 56 | private 57 | 58 | def settings 59 | @settings ||= {} 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/raddocs/middleware.rb: -------------------------------------------------------------------------------- 1 | module Raddocs 2 | # Rack middleware 3 | # 4 | # This lets you cURL for documentation. 5 | # 6 | # curl -H "Accept: text/docs+plain" http://localhost/orders 7 | # 8 | # This will return all of the docs for a given resource, "orders." It is returned 9 | # as a giant flat file containing all of the documentation. "combined_text" output 10 | # must be selected in `rspec_api_documentation`. 11 | # 12 | # The route matches the folder structure of the docs. 13 | class Middleware 14 | def initialize(app) 15 | @app = app 16 | @file_server = Rack::File.new(Raddocs.configuration.docs_dir) 17 | end 18 | 19 | def call(env) 20 | if env["HTTP_ACCEPT"] =~ Raddocs.configuration.docs_mime_type 21 | env = env.merge({ "PATH_INFO" => File.join(env["PATH_INFO"], "index.txt") }) 22 | response = @file_server.call(env) 23 | 24 | if response[0] == 404 25 | body = "Docs are not available for this resource.\n" 26 | response = [404, {"Content-Type" => "type/plain", "Content-Length" => body.size.to_s}, [body]] 27 | end 28 | 29 | response 30 | else 31 | @app.call(env) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/raddocs/models.rb: -------------------------------------------------------------------------------- 1 | module Raddocs 2 | # Index page model 3 | class Index 4 | def initialize(file) 5 | @attrs = JSON.parse(File.read(file)) 6 | end 7 | 8 | # @return [Array] array of {Raddocs::Resource Resources} 9 | def resources 10 | @attrs.fetch("resources", {}).map do |resource| 11 | Resource.new(resource["name"], resource["explanation"], resource["examples"]) 12 | end 13 | end 14 | end 15 | 16 | # Group of examples related to a specific resource, eg "Orders" 17 | class Resource < Struct.new(:name, :explanation, :examples) 18 | # @return [Array] array of {Raddocs::IndexExample IndexExamples} 19 | def examples 20 | @examples ||= super.map do |example| 21 | IndexExample.new(example) 22 | end 23 | end 24 | 25 | def explanation? 26 | !explanation.nil? 27 | end 28 | end 29 | 30 | # Example model for the index page 31 | # 32 | # Has an extra link attribute that is required only on this page. 33 | class IndexExample 34 | attr_reader :description, :link 35 | 36 | def initialize(attributes) 37 | @description = attributes.fetch("description") 38 | @link = attributes.fetch("link") 39 | end 40 | 41 | # Link to example page is the same name as the file minus ".json" 42 | def href 43 | link.gsub(".json", "") 44 | end 45 | end 46 | 47 | # Example page model 48 | class Example 49 | attr_reader :resource, :resource_explanation, :description, :explanation, :parameters, :response_fields, 50 | :requests 51 | 52 | def initialize(file) 53 | @attrs = JSON.parse(File.read(file)) 54 | @resource_explanation = @attrs.fetch("resource_explanation", nil) 55 | @resource = @attrs.fetch("resource") 56 | 57 | @description = @attrs.fetch("description") 58 | @explanation = @attrs.fetch("explanation", nil) 59 | @parameters = Parameters.new(@attrs.fetch("parameters")) 60 | @response_fields = ResponseFields.new(@attrs.fetch("response_fields", [])) 61 | @requests = @attrs.fetch("requests").map { |request| Request.new(request) } 62 | end 63 | 64 | # @return [Boolean] true if explanation is present 65 | def explanation? 66 | !explanation.nil? 67 | end 68 | 69 | def resource_explanation? 70 | !resource_explanation.nil? 71 | end 72 | end 73 | 74 | # Guide page model 75 | class Guide 76 | attr_reader :title 77 | 78 | def initialize(attributes) 79 | @title = attributes.fetch("title") 80 | @file = attributes.fetch("file") 81 | end 82 | 83 | def href 84 | filename = @file.gsub(".md", "") 85 | "guides/#{filename}" 86 | end 87 | end 88 | 89 | # An example's parameters, requires a class because the table can display unknown columns 90 | class Parameters 91 | attr_reader :extra_keys, :params 92 | 93 | SPECIAL_KEYS = ["name", "description", "required", "scope"] 94 | 95 | # Collection object for parameters to pull out unknown keys so they can be 96 | # displayed on the example page. 97 | # 98 | # @example 99 | # params = Parameters.new([ 100 | # {"name" => "page", "description" => "Page number", "Type" => "Integer"} 101 | # ]) 102 | # params.extra_keys 103 | # # => ["Type"] 104 | # 105 | # @param params [Array] array of {Raddocs::Parameter Parameters} 106 | # 107 | def initialize(params) 108 | @params = params.map { |param| Parameter.new(param) } 109 | @extra_keys = params.flat_map(&:keys).uniq - SPECIAL_KEYS 110 | end 111 | 112 | # @return [Boolean] true if params contains elements 113 | def present? 114 | @params.count > 0 115 | end 116 | end 117 | 118 | # Parameter of a request 119 | # 120 | # Can have an unknown columns 121 | # 122 | # @example 123 | # Parameter.new({ 124 | # "name" => "page", 125 | # "description" => "Page number", 126 | # "Type" => "Integer" 127 | # }) 128 | # 129 | class Parameter 130 | attr_reader :name, :description, :required, :scope 131 | 132 | # @param attributes [Hash] 133 | # @option attributes [String] "name" Required 134 | # @option attributes [String] "description" Required 135 | # @option attributes [boolean] "required" defaults to false 136 | # @option attributes [String] "scope" Scope of the parameter, eg 'order[]', defaults to nil 137 | def initialize(attributes) 138 | @attrs = attributes 139 | 140 | @name = attributes.fetch("name") 141 | @description = attributes.fetch("description") 142 | @required = attributes.fetch("required", false) 143 | @scope = attributes.fetch("scope", nil) 144 | end 145 | 146 | # @return [Boolean] true if required is true 147 | def required? 148 | !!@required 149 | end 150 | 151 | # @return [Boolean] true if scope is present 152 | def scope? 153 | !!@scope 154 | end 155 | 156 | def scope 157 | Array(@scope).each_with_index.map do |scope, index| 158 | if index == 0 159 | scope 160 | else 161 | "[#{scope}]" 162 | end 163 | end.join 164 | end 165 | 166 | # Allows unknown keys to be accessed 167 | # @param key [String] 168 | # @return [Object] 169 | def [](key) 170 | @attrs[key] 171 | end 172 | end 173 | 174 | # An example's response fields, requires a class because the table can display 175 | # unknown columns 176 | class ResponseFields 177 | attr_reader :extra_keys, :fields 178 | 179 | SPECIAL_KEYS = ["name", "description", "scope"] 180 | 181 | def initialize(response_fields) 182 | return unless response_fields # Might not be present 183 | @fields = response_fields.map { |field| ResponseField.new(field) } 184 | @extra_keys = response_fields.flat_map(&:keys).uniq - SPECIAL_KEYS 185 | end 186 | 187 | # @return [Boolean] true if fields contains elements 188 | def present? 189 | @fields.count > 0 190 | end 191 | end 192 | 193 | # Fields of a response 194 | # 195 | # Can have an unknown columns 196 | # 197 | # @example 198 | # Parameter.new({ 199 | # "name" => "page", 200 | # "description" => "Page number", 201 | # "Type" => "Integer" 202 | # }) 203 | # 204 | class ResponseField 205 | attr_reader :name, :description, :scope 206 | 207 | def initialize(attributes) 208 | @attrs = attributes 209 | 210 | @name = attributes.fetch("name") 211 | @description = attributes.fetch("description") 212 | @scope = attributes.fetch("scope", nil) 213 | end 214 | 215 | # @return [Boolean] true if scope is present 216 | def scope? 217 | !!@scope 218 | end 219 | 220 | # Allows unknown keys to be accessed 221 | # @param key [String] 222 | # @return [Object] 223 | def [](key) 224 | @attrs[key] 225 | end 226 | end 227 | 228 | # Documented response 229 | # 230 | # @param attributes [Hash] 231 | class Request 232 | attr_reader :request_method, :request_path, :request_body, 233 | :curl, :response_status, :response_body 234 | 235 | # @param attributes [Hash] 236 | # @option attributes [Hash] "request_headers" 237 | # Hash of request headers, not in rack format 238 | # @option attributes [String] "request_method" 239 | # @option attributes [String] "request_path" 240 | # @option attributes [Hash] "request_query_parameters" 241 | # Query parameters pulled from the request if a GET request 242 | # @option attributes [String] "request_body" 243 | # @option attributes [String] "curl" Formatted 244 | # cURL request 245 | # @option attributes [String] "response_status" 246 | # @option attributes [Hash] "response_headers" 247 | # Hash of response headers, not in rack format 248 | # @option attributes [String] "response_body" 249 | def initialize(attributes) 250 | @attrs = attributes 251 | 252 | @request_headers = attributes.fetch("request_headers") 253 | @request_method = attributes.fetch("request_method") 254 | @request_path = attributes.fetch("request_path") 255 | @request_query_parameters = attributes.fetch("request_query_parameters", nil) 256 | @request_body = attributes.fetch("request_body", nil) 257 | @curl = attributes.fetch("curl", nil) 258 | @response_status = attributes.fetch("response_status") 259 | @response_headers = attributes.fetch("response_headers", {}) 260 | @response_body = attributes.fetch("response_body", nil) 261 | end 262 | 263 | # There are unwanted indents if this was a simple each and output in haml 264 | def request_headers 265 | @request_headers.map do |header, value| 266 | "#{header}: #{value}" 267 | end.join("\n") 268 | end 269 | 270 | # @return [String] joined query parameters, eg: "key=value\nkey=value" 271 | def request_query_parameters 272 | @request_query_parameters.map { |k,v| "#{k}=#{v}" }.join("\n") 273 | end 274 | 275 | # @return [Boolean] true if request query parameters are present 276 | def request_query_parameters? 277 | !@request_query_parameters.empty? 278 | end 279 | 280 | # @return [Boolean] true if request body is present 281 | def request_body? 282 | !@request_body.nil? 283 | end 284 | 285 | # @return [Boolean] true if request headers are present 286 | def request_headers? 287 | request_headers.length > 0 288 | end 289 | 290 | # Request headers must be set 291 | # @return [String] Content type of the request 292 | def request_content_type 293 | @request_headers["Content-Type"] 294 | end 295 | 296 | # @return [Boolean] true if cURL command is present 297 | def curl? 298 | !@curl.nil? 299 | end 300 | 301 | # @return [Boolean] true if the response is present 302 | def response? 303 | !@response_status.nil? 304 | end 305 | 306 | # There are unwanted indents if this was a simple each and output in haml 307 | def response_headers 308 | @response_headers.map do |header, value| 309 | "#{header}: #{value}" 310 | end.join("\n") 311 | end 312 | 313 | # @return [Boolean] true if response body is present 314 | def response_body? 315 | !@response_body.nil? 316 | end 317 | 318 | # @return [Boolean] true if response headers are present 319 | def response_headers? 320 | response_headers.length > 0 321 | end 322 | 323 | # Response headers must be set 324 | # @return [String] Content type of the response 325 | def response_content_type 326 | @response_headers["Content-Type"] 327 | end 328 | end 329 | end 330 | -------------------------------------------------------------------------------- /lib/views/example.haml: -------------------------------------------------------------------------------- 1 | .row-fluid 2 | .span4.sidebar 3 | = haml :nav, locals: { index: index, api_name: api_name } 4 | 5 | .span10.main 6 | %script(src="#{url_location}/js/example.js") 7 | 8 | .nav-bar 9 | = link_to "« Back to Index", "/" 10 | 11 | %h1== #{example.resource} API 12 | - if example.resource_explanation? 13 | %p.explanation 14 | = example.resource_explanation 15 | .article 16 | %h2= example.description 17 | 18 | - if example.explanation? 19 | %p.explanation 20 | = example.explanation 21 | 22 | - if example.parameters.present? 23 | %h3 Parameters 24 | %table.parameters 25 | %thead 26 | %tr 27 | %th Name 28 | %th Description 29 | - example.parameters.extra_keys.each do |key| 30 | %th= key 31 | %tbody 32 | - example.parameters.params.each do |param| 33 | %tr.parameter 34 | %td{:class => ("required" if param.required?)} 35 | - if param.scope? 36 | %span.name #{param.scope}[#{param.name}] 37 | - else 38 | %span.name= param.name 39 | %td 40 | %span.description= param.description 41 | - example.parameters.extra_keys.each do |key| 42 | %td 43 | %span.extras= param[key] 44 | 45 | - if example.response_fields.present? 46 | %h3 Response Fields 47 | %table.response-fields 48 | %thead 49 | %tr 50 | %th Name 51 | %th Description 52 | - example.response_fields.extra_keys.each do |key| 53 | %th= key 54 | %tbody 55 | - example.response_fields.fields.each do |field| 56 | %tr.response-field 57 | %td 58 | - if field.scope? 59 | %span.name #{field.scope}[#{field.name}] 60 | - else 61 | %span.name= field.name 62 | %td 63 | %span.description= field.description 64 | - example.response_fields.extra_keys.each do |key| 65 | %td 66 | %span.extras= field[key] 67 | 68 | - example.requests.each_with_index do |request, index| 69 | .request{ :id => "request-#{index}" } 70 | %h3 Request 71 | 72 | - if request.response_headers? 73 | %section.headers 74 | %h4 Headers 75 | %pre.headers 76 | :preserve 77 | #{request.request_headers} 78 | 79 | %section.route 80 | %h4 Route 81 | %pre.route.highlight== #{request.request_method} #{request.request_path} 82 | 83 | - if request.request_query_parameters? 84 | %section.query-parameters 85 | %h4 Query Parameters 86 | %pre.query-parameters.highlight 87 | = request.request_query_parameters 88 | 89 | - if request.request_body? 90 | %section.body 91 | %h4 Body 92 | .content{ "data-content-type" => request.request_content_type } 93 | %textarea 94 | :preserve 95 | #{request.request_body} 96 | 97 | - if request.curl? 98 | %section.curl 99 | %h4 cURL 100 | %pre= request.curl 101 | 102 | - if request.response? 103 | .response 104 | %h3 Response 105 | 106 | - if request.response_headers? 107 | %section.headers 108 | %h4 Headers 109 | %pre.headers 110 | :preserve 111 | #{request.response_headers} 112 | 113 | %section.status 114 | %h4 Status 115 | %pre.status= request.response_status 116 | 117 | - if request.response_body? 118 | %section.body 119 | %h4 Body 120 | .content{ "data-content-type" => request.response_content_type } 121 | %textarea 122 | :preserve 123 | #{request.response_body} 124 | -------------------------------------------------------------------------------- /lib/views/guide.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= api_name %> 5 | <% css_files.each do |file| %> 6 | 7 | <% end %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 33 | 34 | 35 | 36 |
37 | 40 | 41 | <%= Kramdown::Document.new(guide, input: "GFM", syntax_highlighter_opts: { line_numbers: false }).to_html %> 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/views/index.haml: -------------------------------------------------------------------------------- 1 | %h1= api_name 2 | 3 | - if guides.any? 4 | %h2 Guides 5 | %ul.guides 6 | - guides.each do |guide| 7 | %li.guide 8 | = link_to guide.title, "/#{guide.href}" 9 | 10 | - index.resources.each do |resource| 11 | .resource 12 | %h2= resource.name 13 | - if resource.explanation? 14 | %p.explanation 15 | = resource.explanation 16 | %ul.examples 17 | - resource.examples.each do |example| 18 | %li.example 19 | = link_to example.description, "/#{example.href}" 20 | -------------------------------------------------------------------------------- /lib/views/layout.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %title= api_name 5 | 6 | - css_files.each do |file| 7 | %link{:rel => "stylesheet", :href => file} 8 | 9 | %script(src="#{url_location}/js/jquery-1-7-2.js") 10 | %script(src="#{url_location}/js/codemirror.js") 11 | %script(src="#{url_location}/js/mode/css/css.js") 12 | %script(src="#{url_location}/js/mode/htmlmixed/htmlmixed.js") 13 | %script(src="#{url_location}/js/mode/javascript/javascript.js") 14 | %script(src="#{url_location}/js/mode/xml/xml.js") 15 | 16 | :css 17 | td.required .name:after { 18 | float: right; 19 | content: "required"; 20 | font-weight: normal; 21 | color: #F08080; 22 | } 23 | 24 | p { 25 | padding: 15px; 26 | font-size: 130%; 27 | } 28 | 29 | %body 30 | .container 31 | = yield 32 | -------------------------------------------------------------------------------- /lib/views/nav.haml: -------------------------------------------------------------------------------- 1 | 2 | - index.resources.each do |resource| 3 | .resource 4 | %b= resource.name 5 | %ul.examples 6 | - resource.examples.each do |example| 7 | %li.example 8 | = link_to example.description, "/#{example.href}" 9 | -------------------------------------------------------------------------------- /raddocs.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib/', __FILE__) 2 | $:.unshift lib unless $:.include?(lib) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "raddocs" 6 | s.version = "2.3.0" 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Eric Oestrich"] 9 | s.email = ["eric@oestrich.org"] 10 | s.summary = "rspec_api_documentation browser" 11 | s.description = "Browse documentation generated by the rspec_api_documentation gem" 12 | s.homepage = "http://github.com/oestrich/raddocs" 13 | s.license = "MIT" 14 | 15 | s.required_rubygems_version = ">= 1.3.6" 16 | 17 | s.add_runtime_dependency "sinatra", "~> 2.0" 18 | s.add_runtime_dependency "haml", ">= 4.0" 19 | s.add_runtime_dependency "json" 20 | 21 | s.add_development_dependency "rspec", "~> 3.0" 22 | s.add_development_dependency "rack-test", "~> 0.6" 23 | s.add_development_dependency "capybara", "~> 2.3" 24 | s.add_development_dependency "rake", "~> 10.0" 25 | s.add_development_dependency "kramdown" 26 | 27 | s.files = Dir.glob("lib/**/*") 28 | s.require_path = 'lib' 29 | end 30 | -------------------------------------------------------------------------------- /spec/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Raddocs::Configuration do 4 | let(:configuration) { Raddocs::Configuration.new } 5 | 6 | subject { configuration } 7 | 8 | describe ".add_setting" do 9 | before do 10 | Raddocs::Configuration.add_setting :new_setting, :default => "default" 11 | end 12 | 13 | it 'should allow creating a new setting' do 14 | expect(configuration).to respond_to(:new_setting) 15 | expect(configuration).to respond_to(:new_setting=) 16 | end 17 | 18 | it 'should allow setting a default' do 19 | expect(configuration.new_setting).to eq("default") 20 | end 21 | 22 | it "should allow the default setting to be a lambda" do 23 | Raddocs::Configuration.add_setting :another_setting, :default => lambda { |config| config.new_setting } 24 | expect(configuration.another_setting).to eq("default") 25 | end 26 | end 27 | 28 | describe "default settings" do 29 | specify "docs_dir" do 30 | expect(subject.docs_dir).to eq("doc/api") 31 | end 32 | specify "guides_dir" do 33 | expect(subject.guides_dir).to eq("doc/guides") 34 | end 35 | specify "docs_mime_type" do 36 | expect(subject.docs_mime_type).to eq(/text\/docs\+plain/) 37 | end 38 | specify("api_name") do 39 | expect(subject.api_name).to eq("Api Documentation") 40 | end 41 | specify("include_bootstrap") do 42 | expect(subject.include_bootstrap).to be_truthy 43 | end 44 | specify("external_css") do 45 | expect(subject.external_css).to be_empty 46 | end 47 | specify("url_prefix") do 48 | expect(subject.url_prefix).to be_nil 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/example_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Example" do 4 | context "creating an order" do 5 | before do 6 | visit "/orders/creating_an_order" 7 | end 8 | 9 | it "should include custom css and not bootstrap" do 10 | links = page.all("link").map do |link| 11 | link[:href] 12 | end.sort 13 | 14 | expect(links).to eq([ 15 | "/application.css", 16 | "/codemirror.css", 17 | "/custom-css/extra-style.css", 18 | "/custom-css/style.css", 19 | "http://example.com/my-external.css" 20 | ]) 21 | end 22 | 23 | it "should have a link back to index" do 24 | click_link "Back to Index" 25 | 26 | expect(current_path).to eq("/") 27 | end 28 | 29 | it "should have the resource title" do 30 | within("h1") do 31 | expect(page).to have_content("Orders API") 32 | end 33 | end 34 | 35 | it "should have the example description" do 36 | within("h2") do 37 | expect(page).to have_content("Creating an order") 38 | end 39 | end 40 | 41 | it "should have the parameters table" do 42 | within(".parameters") do 43 | parameters = all(".parameter").map do |p| 44 | [p.find(".name").text, p.find(".description").text, p.find(".extras").text] 45 | end 46 | 47 | expect(parameters).to eq([ 48 | ["order[name]", "Name of order", "string"], 49 | ["order[paid]", "If the order has been paid for", "integer"], 50 | ["order[email]", "Email of user that placed the order", "string"] 51 | ]) 52 | end 53 | end 54 | 55 | it "should have the response fields table" do 56 | within(".response-fields") do 57 | response_fields = all(".response-field").map do |p| 58 | [p.find(".name").text, p.find(".description").text, p.find(".extras").text] 59 | end 60 | 61 | expect(response_fields).to eq([ 62 | ["order[name]", "Name of order", "string"], 63 | ["order[paid]", "If the order has been paid for", "integer"], 64 | ["order[email]", "Email of user that placed the order", "string"] 65 | ]) 66 | end 67 | end 68 | 69 | it "should have the requests" do 70 | expect(all(".request").count).to eq(2) 71 | end 72 | 73 | context "requests" do 74 | it "should have the headers" do 75 | within("#request-0 .headers") do 76 | expect(page).to have_content("Accept: application/json") 77 | end 78 | end 79 | 80 | it "should have the route" do 81 | within("#request-0 .route") do 82 | expect(page).to have_content("POST /orders") 83 | end 84 | end 85 | 86 | it "should have the body" do 87 | within("#request-0 .body") do 88 | expect(find("textarea").text).to match(/"order":\{/) 89 | end 90 | end 91 | 92 | it "should have the curl output" do 93 | within("#request-0 .curl") do 94 | expect(page).to have_content("curl \"http://localhost:3000/orders\"") 95 | end 96 | end 97 | 98 | context "response" do 99 | it "should have the headers" do 100 | within("#request-0 .response .headers") do 101 | expect(page).to have_content("Content-Type: application/json") 102 | end 103 | end 104 | 105 | it "should have the status" do 106 | within("#request-0 .response .status") do 107 | expect(page).to have_content("201") 108 | end 109 | end 110 | 111 | it "should have the body" do 112 | within("#request-0 .response .body") do 113 | expect(find("textarea").text).to match(/"email":"email@example\.com"/) 114 | end 115 | end 116 | end 117 | end 118 | 119 | it "should handle visiting a file that does not exist" do 120 | visit "/orders/creating_an_orders" 121 | 122 | expect(page).to have_content("Example does not exist") 123 | end 124 | end 125 | 126 | context "viewing an order" do 127 | before do 128 | visit "/orders/viewing_an_order" 129 | end 130 | 131 | context "requests" do 132 | it "should not have the headers" do 133 | expect(page).to_not have_selector(".request .headers") 134 | end 135 | 136 | context "response" do 137 | it "should not have the headers" do 138 | expect(page).to_not have_selector(".response .headers") 139 | end 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/fixtures/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | Some written documentation about how to authenticate against our API. 4 | -------------------------------------------------------------------------------- /spec/fixtures/guides.yml: -------------------------------------------------------------------------------- 1 | - title: Authentication 2 | file: authentication 3 | -------------------------------------------------------------------------------- /spec/fixtures/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": [ 3 | { 4 | "name": "Orders", 5 | "examples": [ 6 | { 7 | "description": "Creating an order", 8 | "link": "orders/creating_an_order.json", 9 | "groups": "all" 10 | }, 11 | { 12 | "description": "Viewing an order", 13 | "link": "orders/viewing_an_order.json", 14 | "groups": "all" 15 | }, 16 | { 17 | "description": "Deleting an order", 18 | "link": "orders/deleting_an_order.json", 19 | "groups": "all" 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /spec/fixtures/orders/creating_an_order.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "Orders", 3 | "description": "Creating an order", 4 | "explanation": "First, create an order, then make a later request to get it back", 5 | "parameters": [ 6 | { 7 | "name": "name", 8 | "description": "Name of order", 9 | "required": true, 10 | "scope": "order", 11 | "extra-data": "string" 12 | }, 13 | { 14 | "name": "paid", 15 | "description": "If the order has been paid for", 16 | "required": true, 17 | "scope": "order", 18 | "extra-data": "integer" 19 | }, 20 | { 21 | "name": "email", 22 | "description": "Email of user that placed the order", 23 | "scope": "order", 24 | "extra-data": "string" 25 | } 26 | ], 27 | "response_fields": [ 28 | { 29 | "name": "name", 30 | "description": "Name of order", 31 | "scope": "order", 32 | "extra-data": "string" 33 | }, 34 | { 35 | "name": "paid", 36 | "description": "If the order has been paid for", 37 | "scope": "order", 38 | "extra-data": "integer" 39 | }, 40 | { 41 | "name": "email", 42 | "description": "Email of user that placed the order", 43 | "scope": "order", 44 | "extra-data": "string" 45 | } 46 | ], 47 | "requests": [ 48 | { 49 | "request_method": "POST", 50 | "request_path": "/orders", 51 | "request_body": "{\"order\":{\"name\":\"Order 1\",\"paid\":true,\"email\":\"email@example.com\"}}", 52 | "request_headers": { 53 | "Accept": "application/json", 54 | "Content-Type": "application/json", 55 | "Host": "example.org", 56 | "Cookie": "" 57 | }, 58 | "request_query_parameters": {}, 59 | "response_status": 201, 60 | "response_status_text": "Created", 61 | "response_body": "{\"email\":\"email@example.com\",\"name\":\"Order 1\",\"paid\":true}", 62 | "response_headers": { 63 | "Content-Type": "application/json; charset=utf-8", 64 | "Location": "http://example.org/orders/9", 65 | "X-UA-Compatible": "IE=Edge,chrome=1", 66 | "ETag": "\"5fffd66f61cdd4ab8881584dbf5e72cf\"", 67 | "Cache-Control": "max-age=0, private, must-revalidate", 68 | "X-Runtime": "0.006262", 69 | "Content-Length": "58" 70 | }, 71 | "curl": "curl \"http://localhost:3000/orders\" -d \"{\"order\":{\"name\":\"Order 1\",\"paid\":true,\"email\":\"email@example.com\"}}\" -X POST -H \"Accept: application/json\" -H \"Content-Type: application/json\" -H \"Host: example.org\" -H \"Cookie: \"" 72 | }, 73 | { 74 | "request_method": "GET", 75 | "request_path": "/orders/9", 76 | "request_body": null, 77 | "request_headers": { 78 | "Accept": "application/json", 79 | "Content-Type": "application/json", 80 | "Host": "example.org", 81 | "Cookie": "" 82 | }, 83 | "request_query_parameters": {}, 84 | "response_status": 200, 85 | "response_status_text": "OK", 86 | "response_body": "{\"email\":\"email@example.com\",\"name\":\"Order 1\",\"paid\":true}", 87 | "response_headers": { 88 | "Content-Type": "application/json; charset=utf-8", 89 | "X-UA-Compatible": "IE=Edge,chrome=1", 90 | "ETag": "\"5fffd66f61cdd4ab8881584dbf5e72cf\"", 91 | "Cache-Control": "max-age=0, private, must-revalidate", 92 | "X-Runtime": "0.004752", 93 | "Content-Length": "58" 94 | }, 95 | "curl": "curl \"http://localhost:3000/orders/9\" -X GET -H \"Accept: application/json\" -H \"Content-Type: application/json\" -H \"Host: example.org\" -H \"Cookie: \"" 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /spec/fixtures/orders/index.txt: -------------------------------------------------------------------------------- 1 | Creating an Order 2 | -------------------------------------------------------------------------------- /spec/fixtures/orders/viewing_an_order.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "Orders", 3 | "description": "Viewing an order", 4 | "explanation": "Make a request to get an order", 5 | "parameters": [ 6 | ], 7 | "response_fields": [ 8 | { 9 | "name": "name", 10 | "description": "Name of order", 11 | "scope": "order", 12 | "extra-data": "string" 13 | }, 14 | { 15 | "name": "paid", 16 | "description": "If the order has been paid for", 17 | "scope": "order", 18 | "extra-data": "integer" 19 | }, 20 | { 21 | "name": "email", 22 | "description": "Email of user that placed the order", 23 | "scope": "order", 24 | "extra-data": "string" 25 | } 26 | ], 27 | "requests": [ 28 | { 29 | "request_method": "GET", 30 | "request_path": "/orders/9", 31 | "request_body": null, 32 | "request_headers": {}, 33 | "request_query_parameters": {}, 34 | "response_status": 200, 35 | "response_status_text": "OK", 36 | "response_body": "{\"email\":\"email@example.com\",\"name\":\"Order 1\",\"paid\":true}", 37 | "response_headers": {}, 38 | "curl": "curl \"http://localhost:3000/orders/9\" -X GET -H \"Accept: application/json\" -H \"Content-Type: application/json\" -H \"Host: example.org\" -H \"Cookie: \"" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /spec/fixtures/styles/extra-style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartlogic/raddocs/7eaf4454b632da7c01b6ab3e231d7f0cbe6200fa/spec/fixtures/styles/extra-style.css -------------------------------------------------------------------------------- /spec/fixtures/styles/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartlogic/raddocs/7eaf4454b632da7c01b6ab3e231d7f0cbe6200fa/spec/fixtures/styles/style.css -------------------------------------------------------------------------------- /spec/guides_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Viewing Guides" do 4 | before do 5 | visit "/" 6 | end 7 | 8 | specify "guide links are included in the index listing" do 9 | expect(page).to have_selector(".guides") 10 | expect(page).to have_link("Authentication") 11 | end 12 | 13 | specify "loads the guide" do 14 | click_on "Authentication" 15 | 16 | expect(page).to have_content("Authentication") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Index' do 4 | before do 5 | visit "/" 6 | end 7 | 8 | it "should have the api name as the title" do 9 | expect(find("head title")).to have_content("Raddocs Test") 10 | end 11 | 12 | it "should have the api name" do 13 | expect(find("h1")).to have_content("Raddocs Test") 14 | end 15 | 16 | it "should have the resource title" do 17 | within(".resource") do 18 | expect(page).to have_content("Orders") 19 | end 20 | end 21 | 22 | it "should have the examples" do 23 | within(".examples") do 24 | examples = all(".example").map { |e| e.text.strip } 25 | expect(examples).to eq(["Creating an order", "Viewing an order", "Deleting an order"]) 26 | end 27 | end 28 | 29 | it "should link to the examples" do 30 | click_link "Creating an order" 31 | 32 | within("h1") do 33 | expect(page).to have_content("Orders API") 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/middleware_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Raddocs::Middleware do 4 | include Rack::Test::Methods 5 | 6 | before do 7 | Raddocs.configure do |config| 8 | config.docs_mime_type = /text\/docs\+plain/ 9 | end 10 | end 11 | 12 | let(:app) { 13 | Rack::Builder.new do 14 | use Raddocs::Middleware 15 | run lambda { |env| 16 | [200, {}, ["Not intercepted"]] 17 | } 18 | end 19 | } 20 | 21 | it "should only respond when accept is correct" do 22 | get "/orders", {}, "HTTP_ACCEPT" => "application/json" 23 | 24 | expect(last_response.body).to eq("Not intercepted") 25 | end 26 | 27 | it "should intercept doc requests" do 28 | get "/orders", {}, "HTTP_ACCEPT" => "text/docs+plain" 29 | 30 | expect(last_response.body).to eq("Creating an Order\n") 31 | end 32 | 33 | it "should notify of no docs" do 34 | get "/accounts", {}, "HTTP_ACCEPT" => "text/docs+plain" 35 | 36 | expect(last_response.body).to eq("Docs are not available for this resource.\n") 37 | expect(last_response.status).to eq(404) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/mountable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Mountable" do 4 | before do 5 | Capybara.app = Rack::Builder.new do 6 | map "/docs" do 7 | run Raddocs::App 8 | end 9 | end 10 | 11 | visit "/docs" 12 | end 13 | 14 | it "should show the index page" do 15 | expect(page).to have_content("Raddocs Test") 16 | end 17 | 18 | it "should update the links" do 19 | click_link "Creating an order" 20 | 21 | expect(current_path).to eq("/docs/orders/creating_an_order") 22 | expect(page).to have_content("Orders API") 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/parameter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Raddocs::Parameter do 4 | specify "scope is a single level" do 5 | parameter = Raddocs::Parameter.new({ 6 | "name" => "name", 7 | "description" => "Name of order", 8 | "scope" => "order", 9 | }) 10 | 11 | expect(parameter.scope).to eq("order") 12 | end 13 | 14 | specify "scope is a multi-level" do 15 | parameter = Raddocs::Parameter.new({ 16 | "name" => "name", 17 | "description" => "Name of order", 18 | "scope" => ["order", "attributes"], 19 | }) 20 | 21 | expect(parameter.scope).to eq("order[attributes]") 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/parameters_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Raddocs::Parameters do 4 | let(:parameters_hash) { [ 5 | { 6 | "name" => "name", 7 | "description" => "Name of order", 8 | "required" => true, 9 | "scope" => "order", 10 | "Extra Data" => "string" 11 | }, 12 | { 13 | "name" => "paid", 14 | "description" => "If the order has been paid for", 15 | "required" => true, 16 | "scope" => "order", 17 | "more-data" => "true" 18 | } 19 | ] } 20 | 21 | it "should handle extra parameters" do 22 | parameters = Raddocs::Parameters.new(parameters_hash) 23 | expect(parameters.extra_keys).to eq(["Extra Data", "more-data"]) 24 | end 25 | 26 | specify "loads params" do 27 | parameters = Raddocs::Parameters.new(parameters_hash) 28 | expect(parameters.params.count).to eq(2) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/raddocs_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Raddocs do 4 | describe ".configuration" do 5 | it "should be a configuration" do 6 | expect(Raddocs.configuration).to be_a(Raddocs::Configuration) 7 | end 8 | end 9 | 10 | describe ".configuration" do 11 | it "should yeild the configuration" do 12 | Raddocs.configure do |config| 13 | expect(config).to be_a(Raddocs::Configuration) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/response_fields_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Raddocs::ResponseFields do 4 | let(:parameters_raw) { [ 5 | { 6 | "name" => "name", 7 | "description" => "Name of order", 8 | "scope" => "order", 9 | "Extra Data" => "string" 10 | }, 11 | { 12 | "name" => "paid", 13 | "description" => "If the order has been paid for", 14 | "scope" => "order", 15 | "more-data" => "true" 16 | } 17 | ] } 18 | 19 | it "should handle extra keys" do 20 | response_fields = Raddocs::ResponseFields.new(parameters_raw) 21 | expect(response_fields.extra_keys).to eq(["Extra Data", "more-data"]) 22 | end 23 | 24 | it "should handle no data" do 25 | response_fields = Raddocs::ResponseFields.new(nil) 26 | expect(response_fields.extra_keys).to be_nil 27 | expect(response_fields.fields).to be_nil 28 | end 29 | 30 | it "should handle empty array" do 31 | response_fields = Raddocs::ResponseFields.new([]) 32 | expect(response_fields.extra_keys).to eq([]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'raddocs' 2 | require 'rack/test' 3 | require 'capybara/rspec' 4 | 5 | module Raddocs 6 | class App 7 | set :environment, :test 8 | end 9 | end 10 | 11 | Capybara.configure do |config| 12 | config.match = :prefer_exact 13 | config.ignore_hidden_elements = false 14 | end 15 | 16 | RSpec.configure do |config| 17 | config.include Capybara::DSL 18 | 19 | config.before do 20 | Capybara.app = Raddocs::App 21 | end 22 | end 23 | 24 | Raddocs.configure do |config| 25 | config.api_name = "Raddocs Test" 26 | config.docs_dir = "spec/fixtures" 27 | config.guides_dir = "spec/fixtures" 28 | config.external_css = "http://example.com/my-external.css" 29 | config.include_bootstrap = false 30 | end 31 | --------------------------------------------------------------------------------