├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Eusonlito │ └── LaravelMeta │ │ ├── Facade.php │ │ ├── FixesTrait.php │ │ ├── Meta.php │ │ ├── MetaServiceProvider.php │ │ └── Tags │ │ ├── DCtag.php │ │ ├── MetaName.php │ │ ├── MetaProduct.php │ │ ├── MetaProperty.php │ │ ├── Tag.php │ │ ├── TagAbstract.php │ │ ├── TagInterface.php │ │ └── TwitterCard.php └── config │ └── config.php └── tests ├── Tests.php └── resources ├── image-1.png ├── image-2.jpg ├── scripts-1.js ├── scripts-2.js ├── styles-1.css └── styles-2.css /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | - 7.2 10 | - 7.3 11 | - 7.4 12 | - 8.0 13 | - 8.1 14 | - 8.2 15 | 16 | before_script: 17 | - composer install --dev --prefer-source --no-interaction 18 | 19 | script: vendor/bin/phpunit 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cees van Egmond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML Meta Tags management package available for Laravel >= 5 (Including 10) 2 | 3 | [![Build Status](https://travis-ci.org/eusonlito/laravel-Meta.svg?branch=master)](https://travis-ci.org/eusonlito/laravel-Meta) 4 | [![Latest Stable Version](https://poser.pugx.org/eusonlito/laravel-meta/v/stable.png)](https://packagist.org/packages/eusonlito/laravel-meta) 5 | [![Total Downloads](https://poser.pugx.org/eusonlito/laravel-meta/downloads.png)](https://packagist.org/packages/eusonlito/laravel-meta) 6 | [![License](https://poser.pugx.org/eusonlito/laravel-meta/license.png)](https://packagist.org/packages/eusonlito/laravel-meta) 7 | 8 | With this package you can manage header Meta Tags from Laravel controllers. 9 | 10 | If you want a Laravel <= 4.2 compatible version, please use `v4.2` branch. 11 | 12 | ## Installation 13 | 14 | Begin by installing this package through Composer. 15 | 16 | ```js 17 | { 18 | "require": { 19 | "eusonlito/laravel-meta": "3.1.*" 20 | } 21 | } 22 | ``` 23 | 24 | ### Laravel installation 25 | 26 | ```php 27 | 28 | // config/app.php 29 | 30 | 'providers' => [ 31 | '...', 32 | Eusonlito\LaravelMeta\MetaServiceProvider::class 33 | ]; 34 | 35 | 'aliases' => [ 36 | '...', 37 | 'Meta' => Eusonlito\LaravelMeta\Facade::class, 38 | ]; 39 | ``` 40 | 41 | Now you have a ```Meta``` facade available. 42 | 43 | Publish the config file: 44 | 45 | ``` 46 | php artisan vendor:publish --provider="Eusonlito\LaravelMeta\MetaServiceProvider" 47 | ``` 48 | 49 | #### app/Http/Controllers/Controller.php 50 | 51 | ```php 52 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | {!! Meta::get('title') !!} 142 | 143 | {!! Meta::tag('robots') !!} 144 | 145 | {!! Meta::tag('site_name', 'My site') !!} 146 | {!! Meta::tag('url', Request::url()); !!} 147 | {!! Meta::tag('locale', 'en_EN') !!} 148 | 149 | {!! Meta::tag('title') !!} 150 | {!! Meta::tag('description') !!} 151 | 152 | {!! Meta::tag('canonical') !!} 153 | 154 | {{-- Print custom section images and a default image after that --}} 155 | {!! Meta::tag('image', asset('images/default-logo.png')) !!} 156 | 157 | 158 | 159 | ... 160 | 161 | 162 | ``` 163 | 164 | Or you can use Blade directives: 165 | 166 | ```php 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | {!! Meta::get('title') !!} 176 | 177 | @meta('robots') 178 | 179 | @meta('site_name', 'My site') 180 | @meta('url', Request::url()) 181 | @meta('locale', 'en_EN') 182 | 183 | @meta('title') 184 | @meta('description') 185 | 186 | @meta('canonical') 187 | 188 | {{-- Print custom section images and a default image after that --}} 189 | @meta('image', asset('images/default-logo.png')) 190 | 191 | {{-- Or use @metas to get all tags at once --}} 192 | @metas 193 | 194 | 195 | 196 | 197 | ... 198 | 199 | 200 | ``` 201 | 202 | ### MetaProduct / og:product 203 | This will allow you to add product data to your meta data. See [Open Graph product object](https://developers.facebook.com/docs/payments/product/) 204 | ```php 205 | // resources/views/html.php 206 | 207 | 208 | ... 209 | {!! Meta::tag('type') !!} // this is needed for Meta Product to change the og:type to og:product 210 | {!! Meta::tag('product') !!} 211 | 212 | 213 | ``` 214 | 215 | Add your product data from your controller 216 | 217 | ```php 218 | 100, 229 | 'currency' => 'EUR', 230 | ]); 231 | 232 | # if multiple currencies just add more product metas 233 | Meta::set('product', [ 234 | 'price' => 100, 235 | 'currency' => 'USD', 236 | ]); 237 | 238 | return view('index'); 239 | } 240 | } 241 | ``` 242 | 243 | ### Config 244 | 245 | ```php 246 | return [ 247 | /* 248 | |-------------------------------------------------------------------------- 249 | | Limit title meta tag length 250 | |-------------------------------------------------------------------------- 251 | | 252 | | To best SEO implementation, limit tags. 253 | | 254 | */ 255 | 256 | 'title_limit' => 70, 257 | 258 | /* 259 | |-------------------------------------------------------------------------- 260 | | Limit description meta tag length 261 | |-------------------------------------------------------------------------- 262 | | 263 | | To best SEO implementation, limit tags. 264 | | 265 | */ 266 | 267 | 'description_limit' => 200, 268 | 269 | /* 270 | |-------------------------------------------------------------------------- 271 | | Limit image meta tag quantity 272 | |-------------------------------------------------------------------------- 273 | | 274 | | To best SEO implementation, limit tags. 275 | | 276 | */ 277 | 278 | 'image_limit' => 5, 279 | 280 | /* 281 | |-------------------------------------------------------------------------- 282 | | Available Tag formats 283 | |-------------------------------------------------------------------------- 284 | | 285 | | A list of tags formats to print with each definition 286 | | 287 | */ 288 | 289 | 'tags' => ['Tag', 'MetaName', 'MetaProperty', 'MetaProduct', 'TwitterCard'], 290 | ]; 291 | ``` 292 | 293 | ### Using Meta outside Laravel 294 | 295 | #### Controller 296 | 297 | ```php 298 | require __DIR__.'/vendor/autoload.php'; 299 | 300 | // Check default settings 301 | $config = require __DIR__.'/src/config/config.php'; 302 | 303 | $Meta = new Eusonlito\LaravelMeta\Meta($config); 304 | 305 | # Default title 306 | $Meta->title('This is default page title to complete section title'); 307 | 308 | # Default robots 309 | $Meta->set('robots', 'index,follow'); 310 | 311 | # Section description 312 | $Meta->set('title', 'This is a detail page'); 313 | $Meta->set('description', 'All about this detail page'); 314 | $Meta->set('image', '/images/detail-logo.png'); 315 | 316 | # Canonical URL 317 | $Meta->set('canonical', 'http://example.com'); 318 | ``` 319 | 320 | #### Template 321 | 322 | ```php 323 | <?= $Meta->get('title'); ?> 324 | 325 | tag('robots'); ?> 326 | 327 | tag('site_name', 'My site'); ?> 328 | tag('url', getenv('REQUEST_URI')); ?> 329 | tag('locale', 'en_EN'); ?> 330 | 331 | tag('title'); ?> 332 | tag('description'); ?> 333 | 334 | tag('canonical'); ?> 335 | 336 | # Print custom section image and a default image after that 337 | tag('image', '/images/default-logo.png'); ?> 338 | ``` 339 | 340 | #### Updates from 2.* 341 | 342 | * ``Meta::meta('title', 'Section Title')`` > ``Meta::set('title', 'Section Title')`` 343 | * ``Meta::meta('title')`` > ``Meta::get('title')`` 344 | * ``Meta::tagMetaName('title')`` > ``Meta::tag('title')`` 345 | * ``Meta::tagMetaProperty('title')`` > ``Meta::tag('title')`` 346 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eusonlito/laravel-meta", 3 | "keywords": ["meta", "tags", "head", "laravel"], 4 | "description": "A package to manage Header Meta Tags", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Lito", 9 | "email": "lito@eordes.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.6" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^9.3" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Eusonlito\\LaravelMeta\\": "src/Eusonlito/LaravelMeta/" 21 | } 22 | }, 23 | "extra": { 24 | "laravel": { 25 | "providers": [ 26 | "Eusonlito\\LaravelMeta\\MetaServiceProvider" 27 | ], 28 | "aliases": { 29 | "Meta": "Eusonlito\\LaravelMeta\\Facade" 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Facade.php: -------------------------------------------------------------------------------- 1 | config[$key.'_limit'])) { 29 | $limit = $this->config[$key.'_limit']; 30 | } else { 31 | return $text; 32 | } 33 | 34 | $length = mb_strlen($text); 35 | 36 | if ($length <= (int) $limit) { 37 | return $text; 38 | } 39 | 40 | $text = mb_substr($text, 0, ($limit -= 3)); 41 | 42 | if ($space = mb_strrpos($text, ' ')) { 43 | $text = mb_substr($text, 0, $space); 44 | } 45 | 46 | return $text.'...'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Meta.php: -------------------------------------------------------------------------------- 1 | 70, 30 | 'description_limit' => 200, 31 | 'image_limit' => 5, 32 | 'tags' => ['Tag', 'MetaName', 'MetaProperty', 'MetaProduct', 'TwitterCard'], 33 | 'separator' => ' - ' 34 | ]; 35 | 36 | /** 37 | * @var object; 38 | */ 39 | protected static $instance; 40 | 41 | /** 42 | * @param array $config = [] 43 | * 44 | * @return object 45 | */ 46 | public static function getInstance(array $config = []) 47 | { 48 | return static::$instance ?: (static::$instance = new self($config)); 49 | } 50 | 51 | /** 52 | * @param array $config = [] 53 | * 54 | * @return this 55 | */ 56 | public function __construct($config = []) 57 | { 58 | if (!empty($config)) { 59 | $this->setConfig($config); 60 | } 61 | 62 | $this->metas['image'] = []; 63 | } 64 | 65 | /** 66 | * @param array $config = [] 67 | * 68 | * @return this 69 | */ 70 | public function setConfig(array $config = []) 71 | { 72 | $config = $config + $this->config; 73 | 74 | foreach ($this->defaults as $key => $value) { 75 | if (!array_key_exists($key, $config)) { 76 | $config[$key] = $value; 77 | } 78 | } 79 | 80 | $this->config = $config; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * @param string|null $title = null 87 | * 88 | * @return string 89 | */ 90 | public function title($title = null) 91 | { 92 | if ($title === null) { 93 | return $this->title; 94 | } 95 | 96 | $title = $this->plain($title); 97 | 98 | if (empty($this->metas['title'])) { 99 | $this->metas['title'] = $title; 100 | } 101 | 102 | return $this->title = $title; 103 | } 104 | 105 | /** 106 | * @param string $key 107 | * @param string $value 108 | * 109 | * @return string 110 | */ 111 | public function set($key, $value) 112 | { 113 | if (!is_array($value)) { 114 | $value = $this->plain($value); 115 | } 116 | 117 | $method = 'set'.$key; 118 | 119 | if (method_exists($this, $method)) { 120 | return $this->$method($value); 121 | } 122 | 123 | return $this->metas[$key] = self::cut($value, $key); 124 | } 125 | 126 | /** 127 | * @param string $key 128 | * 129 | * @return void 130 | */ 131 | public function remove($key) 132 | { 133 | $method = 'remove'.$key; 134 | 135 | if (method_exists($this, $method)) { 136 | $this->$method(); 137 | } else { 138 | unset($this->metas[$key]); 139 | } 140 | } 141 | 142 | /** 143 | * @param string $value 144 | * 145 | * @return string 146 | */ 147 | protected function setTitle($value) 148 | { 149 | $title = $this->title; 150 | 151 | if ($title && $this->config['title_limit']) { 152 | $title = $this->config['separator'].$title; 153 | $limit = $this->config['title_limit'] - mb_strlen($title); 154 | } else { 155 | $limit = 'title'; 156 | } 157 | 158 | return $this->metas['title'] = self::cut($value, $limit).$title; 159 | } 160 | 161 | /** 162 | * @param string $value 163 | * 164 | * @return string 165 | */ 166 | protected function setImage($value) 167 | { 168 | if (count($this->metas['image']) >= $this->config['image_limit']) { 169 | return ''; 170 | } 171 | 172 | $this->metas['image'][] = $value; 173 | 174 | return $value; 175 | } 176 | 177 | /** 178 | * @return void 179 | */ 180 | protected function removeImage() 181 | { 182 | $this->metas['image'] = []; 183 | } 184 | 185 | /** 186 | * @param string $value 187 | * 188 | * @return string 189 | */ 190 | protected function setProduct($value) 191 | { 192 | $this->metas['product'][] = $value; 193 | 194 | $this->set('type', 'og:product'); 195 | 196 | return $value; 197 | } 198 | 199 | /** 200 | * @param string $key 201 | * @param string|array $default = '' 202 | * 203 | * @return string|array 204 | */ 205 | public function get($key, $default = '') 206 | { 207 | $method = 'get'.$key; 208 | 209 | if (method_exists($this, $method)) { 210 | return $this->$method($default); 211 | } 212 | 213 | if (empty($this->metas[$key])) { 214 | return $default; 215 | } 216 | 217 | return $this->metas[$key]; 218 | } 219 | 220 | /** 221 | * @param string|array $default 222 | * 223 | * @return array 224 | */ 225 | public function getImage($default) 226 | { 227 | if ($default) { 228 | $default = is_array($default) ? $default : [$default]; 229 | } else { 230 | $default = []; 231 | } 232 | 233 | return array_slice(array_merge($this->metas['image'], $default), 0, $this->config['image_limit']); 234 | } 235 | 236 | /** 237 | * @return array 238 | */ 239 | public function getProduct() 240 | { 241 | return $this->metas['product']; 242 | } 243 | 244 | /** 245 | * @param string $key 246 | * @param string|array $default = '' 247 | * 248 | * @return string 249 | */ 250 | public function tag($key, $default = '') 251 | { 252 | if (!($values = $this->get($key, $default))) { 253 | return ''; 254 | } 255 | 256 | if (!is_array($values)) { 257 | $values = [$values]; 258 | } 259 | 260 | $html = ''; 261 | 262 | foreach ($this->config['tags'] as $tag) { 263 | $class = __NAMESPACE__.'\\Tags\\'.$tag; 264 | 265 | foreach (array_unique($values) as $value) { 266 | $html .= "\n".$class::tag($key, $value); 267 | } 268 | } 269 | 270 | return $html; 271 | } 272 | 273 | /** 274 | * @param array $keys = [] 275 | * 276 | * @return string 277 | */ 278 | public function tags(array $keys = []) 279 | { 280 | $html = ''; 281 | 282 | foreach (($keys ?: array_keys($this->metas)) as $key) { 283 | $html .= "\n".$this->tag($key); 284 | } 285 | 286 | return $html; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/MetaServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 24 | __DIR__.'/../../config/config.php' => config_path('meta.php') 25 | ]); 26 | 27 | $this->addBladeDirectives(); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->app->singleton(Meta::class, function () { 38 | return new Meta(config('meta')); 39 | }); 40 | 41 | $this->app->alias(Meta::class, 'meta'); 42 | } 43 | 44 | /** 45 | * Get the services provided by the provider. 46 | * 47 | * @return array 48 | */ 49 | public function provides() 50 | { 51 | return [Meta::class, 'meta']; 52 | } 53 | 54 | /** 55 | * Register blade directives 56 | * 57 | * @return void 58 | */ 59 | protected function addBladeDirectives() 60 | { 61 | $this->app->afterResolving('blade.compiler', function (BladeCompiler $bladeCompiler) { 62 | $bladeCompiler->directive('meta', function ($arguments) { 63 | return ""; 64 | }); 65 | 66 | $bladeCompiler->directive('metas', function ($arguments) { 67 | return ""; 68 | }); 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/DCtag.php: -------------------------------------------------------------------------------- 1 | '; 14 | } 15 | } 16 | 17 | public static function propertyTag($key) 18 | { 19 | $tag = 'DC.'; 20 | 21 | switch ($key) { 22 | case 'locale': 23 | return $tag.'Language'; 24 | 25 | case 'canonical': 26 | return $tag.'Relation'; 27 | 28 | case 'keywords': 29 | return $tag.'Subject'; 30 | 31 | default: 32 | return $tag.ucfirst($key); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/MetaName.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/MetaProduct.php: -------------------------------------------------------------------------------- 1 | $value) { 19 | if (in_array($key, self::$available, true)) { 20 | $html .= ''; 21 | } 22 | } 23 | 24 | return $html; 25 | } 26 | 27 | public static function propertyTag($key) 28 | { 29 | $tag = 'product:'; 30 | 31 | switch ($key) { 32 | case 'amount': 33 | case 'price': 34 | $tag .= 'price:amount'; 35 | break; 36 | case 'currency': 37 | $tag .= 'price:currency'; 38 | break; 39 | } 40 | 41 | return $tag; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/MetaProperty.php: -------------------------------------------------------------------------------- 1 | '; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/Tag.php: -------------------------------------------------------------------------------- 1 | '.$value.''; 13 | } 14 | } 15 | 16 | public static function tagImage($key, $value) 17 | { 18 | return ''; 19 | } 20 | 21 | public static function tagCanonical($key, $value) 22 | { 23 | return ''; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelMeta/Tags/TagAbstract.php: -------------------------------------------------------------------------------- 1 | '; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | 70, 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Limit description meta tag length 17 | |-------------------------------------------------------------------------- 18 | | 19 | | To best SEO implementation, limit tags. 20 | | 21 | */ 22 | 23 | 'description_limit' => 200, 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Limit image meta tag quantity 28 | |-------------------------------------------------------------------------- 29 | | 30 | | To best SEO implementation, limit tags. 31 | | 32 | */ 33 | 34 | 'image_limit' => 5, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Available Tag formats 39 | |-------------------------------------------------------------------------- 40 | | 41 | | A list of tags formats to print with each definition 42 | | 43 | */ 44 | 45 | 'tags' => ['Tag', 'MetaName', 'MetaProperty', 'MetaProduct', 'TwitterCard', 'DCtag'], 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Strings Separator 50 | |-------------------------------------------------------------------------- 51 | | 52 | | Use this string to separate default and custom title. 53 | | 54 | */ 55 | 56 | 'separator' => ' - ', 57 | ]; 58 | -------------------------------------------------------------------------------- /tests/Tests.php: -------------------------------------------------------------------------------- 1 | Meta = new Meta([ 15 | 'title_limit' => 70, 16 | 'description_limit' => 200, 17 | 'image_limit' => 5 18 | ]); 19 | } 20 | 21 | protected static function text($length) 22 | { 23 | $base = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 24 | $text = ''; 25 | 26 | while (mb_strlen($text) < $length) { 27 | $text .= str_shuffle($base); 28 | } 29 | 30 | return mb_substr($text, 0, $length); 31 | } 32 | 33 | public function testMetaTitle() 34 | { 35 | $response = $this->Meta->set('title', $text = self::text(50)); 36 | 37 | $this->assertTrue($text === $response); 38 | 39 | $response = $this->Meta->set('title', $text = self::text(80)); 40 | 41 | $this->assertNotTrue($text, $response); 42 | $this->assertTrue(mb_strlen($response) === 70); 43 | } 44 | 45 | public function testMetaDescription() 46 | { 47 | $response = $this->Meta->set('description', $text = self::text(50)); 48 | 49 | $this->assertTrue($text === $response); 50 | 51 | $response = $this->Meta->set('description', $text = self::text(250)); 52 | 53 | $this->assertNotTrue($text === $response); 54 | $this->assertTrue(mb_strlen($response) === 200); 55 | } 56 | 57 | public function testMetaTitleWithTitle() 58 | { 59 | $response = $this->Meta->title(self::$title); 60 | 61 | $this->assertTrue(self::$title === $response); 62 | 63 | $response = $this->Meta->set('title', $text = self::text(30)); 64 | 65 | $this->assertTrue($text.' - '.self::$title === $response); 66 | 67 | $response = $this->Meta->set('title', $text = self::text(80)); 68 | 69 | $this->assertNotTrue($text.' - '.self::$title === $response); 70 | $this->assertTrue(mb_strlen($response) === 70); 71 | } 72 | 73 | public function testMetaImage() 74 | { 75 | $response = $this->Meta->set('image', $text = self::text(30)); 76 | 77 | $this->assertTrue($text === $response); 78 | 79 | $response = $this->Meta->set('image', $text = self::text(150)); 80 | 81 | $this->assertTrue($text === $response); 82 | 83 | for ($i = 0; $i < 5; $i++) { 84 | $response = $this->Meta->set('image', $text =self::text(80)); 85 | 86 | if ($i > 2) { 87 | $this->assertTrue(empty($response)); 88 | } else { 89 | $this->assertTrue($text === $response); 90 | } 91 | } 92 | 93 | $this->assertTrue(count($this->Meta->get('image')) === 5); 94 | } 95 | 96 | public function testTagTitle() 97 | { 98 | $this->Meta->title(self::$title); 99 | $this->Meta->set('title', $text = self::text(20)); 100 | 101 | $tag = $this->Meta->tag('title'); 102 | 103 | $this->assertTrue(mb_substr_count($tag, 'assertTrue(mb_substr_count($tag, 'assertTrue(mb_substr_count($tag, 'assertTrue(mb_substr_count($tag, '') === 1); 107 | $this->assertTrue(mb_strstr($tag, self::$title) ? true : false); 108 | $this->assertTrue(mb_strstr($tag, $text) ? true : false); 109 | } 110 | 111 | public function testTagDescription() 112 | { 113 | $this->Meta->set('description', $text = self::text(150)); 114 | 115 | $tag = $this->Meta->tag('description'); 116 | 117 | $this->assertTrue(mb_substr_count($tag, '<meta name="description"') === 1); 118 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:description"') === 1); 119 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:description"') === 1); 120 | $this->assertTrue(mb_substr_count($tag, '<description>') === 0); 121 | $this->assertTrue(mb_strstr($tag, $text) ? true : false); 122 | } 123 | 124 | public function testTagImage() 125 | { 126 | for ($i = 0; $i < 10; $i++) { 127 | $this->Meta->set('image', self::text(80)); 128 | } 129 | 130 | $tag = $this->Meta->tag('image'); 131 | 132 | $this->assertTrue(mb_substr_count($tag, '<meta name="image"') === 5); 133 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:image"') === 5); 134 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:image"') === 5); 135 | $this->assertTrue(mb_substr_count($tag, '<image>') === 0); 136 | $this->assertTrue(mb_substr_count($tag, '<link rel="image_src"') === 5); 137 | } 138 | 139 | public function testTagImageDefault() 140 | { 141 | $tag = $this->Meta->tag('image', self::text(80)); 142 | 143 | $this->assertTrue(mb_substr_count($tag, '<meta name="image"') === 1); 144 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:image"') === 1); 145 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:image"') === 1); 146 | $this->assertTrue(mb_substr_count($tag, '<image>') === 0); 147 | $this->assertTrue(mb_substr_count($tag, '<link rel="image_src"') === 1); 148 | 149 | for ($i = 0; $i < 3; $i++) { 150 | $this->Meta->set('image', self::text(80)); 151 | } 152 | 153 | $tag = $this->Meta->tag('image'); 154 | 155 | $this->assertTrue(mb_substr_count($tag, '<meta name="image"') === 3); 156 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:image"') === 3); 157 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:image"') === 3); 158 | $this->assertTrue(mb_substr_count($tag, '<image>') === 0); 159 | $this->assertTrue(mb_substr_count($tag, '<link rel="image_src"') === 3); 160 | 161 | $tag = $this->Meta->tag('image', self::text(80)); 162 | 163 | $this->assertTrue(mb_substr_count($tag, '<meta name="image"') === 4); 164 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:image"') === 4); 165 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:image"') === 4); 166 | $this->assertTrue(mb_substr_count($tag, '<image>') === 0); 167 | $this->assertTrue(mb_substr_count($tag, '<link rel="image_src"') === 4); 168 | 169 | $tag = $this->Meta->tag('image', self::text(80)); 170 | 171 | $this->assertTrue(mb_substr_count($tag, '<meta name="image"') === 4); 172 | $this->assertTrue(mb_substr_count($tag, '<meta name="twitter:image"') === 4); 173 | $this->assertTrue(mb_substr_count($tag, '<meta property="og:image"') === 4); 174 | $this->assertTrue(mb_substr_count($tag, '<image>') === 0); 175 | $this->assertTrue(mb_substr_count($tag, '<link rel="image_src"') === 4); 176 | } 177 | 178 | public function testTagProduct() 179 | { 180 | 181 | $this->Meta->set('product', [ 182 | 'price' => 100, 183 | 'currency' => 'EUR' 184 | ]); 185 | 186 | $tag = $this->Meta->tag('product'); 187 | $this->assertTrue(mb_substr_count($tag, '<meta property="product:price:amount"') === 1); 188 | $this->assertTrue(mb_substr_count($tag, '<meta property="product:price:currency"') === 1); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/resources/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Meta/2488fad353b9f2ba088d32c5aca6eee39897408c/tests/resources/image-1.png -------------------------------------------------------------------------------- /tests/resources/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Meta/2488fad353b9f2ba088d32c5aca6eee39897408c/tests/resources/image-2.jpg -------------------------------------------------------------------------------- /tests/resources/scripts-2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript requires jQuery') } 8 | 9 | /* ======================================================================== 10 | * Bootstrap: transition.js v3.2.0 11 | * http://getbootstrap.com/javascript/#transitions 12 | * ======================================================================== 13 | * Copyright 2011-2014 Twitter, Inc. 14 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 15 | * ======================================================================== */ 16 | 17 | 18 | +function ($) { 19 | 'use strict'; 20 | 21 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 22 | // ============================================================ 23 | 24 | function transitionEnd() { 25 | var _TEST_INI_FILE2; 26 | 27 | var el = document.createElement('bootstrap') 28 | 29 | var transEndEventNames = { 30 | WebkitTransition : 'webkitTransitionEnd', 31 | MozTransition : 'transitionend', 32 | OTransition : 'oTransitionEnd otransitionend', 33 | transition : 'transitionend' 34 | } 35 | 36 | for (var name in transEndEventNames) { 37 | if (el.style[name] !== undefined) { 38 | return { end: transEndEventNames[name] } 39 | } 40 | } 41 | 42 | return false // explicit for ie8 ( ._.) 43 | } 44 | 45 | // http://blog.alexmaccaw.com/css-transitions 46 | $.fn.emulateTransitionEnd = function (duration) { 47 | var called = false 48 | var $el = this 49 | $(this).one('bsTransitionEnd', function () { called = true }) 50 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 51 | setTimeout(callback, duration) 52 | return this 53 | } 54 | 55 | $(function () { 56 | $.support.transition = transitionEnd() 57 | 58 | if (!$.support.transition) return 59 | 60 | $.event.special.bsTransitionEnd = { 61 | bindType: $.support.transition.end, 62 | delegateType: $.support.transition.end, 63 | handle: function (e) { 64 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 65 | } 66 | } 67 | }) 68 | 69 | }(jQuery); 70 | 71 | /* ======================================================================== 72 | * Bootstrap: alert.js v3.2.0 73 | * http://getbootstrap.com/javascript/#alerts 74 | * ======================================================================== 75 | * Copyright 2011-2014 Twitter, Inc. 76 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 77 | * ======================================================================== */ 78 | 79 | 80 | +function ($) { 81 | 'use strict'; 82 | 83 | // ALERT CLASS DEFINITION 84 | // ====================== 85 | 86 | var dismiss = '[data-dismiss="alert"]' 87 | var Alert = function (el) { 88 | $(el).on('click', dismiss, this.close) 89 | } 90 | 91 | Alert.VERSION = '3.2.0' 92 | 93 | Alert.prototype.close = function (e) { 94 | var $this = $(this) 95 | var selector = $this.attr('data-target') 96 | 97 | if (!selector) { 98 | selector = $this.attr('href') 99 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 100 | } 101 | 102 | var $parent = $(selector) 103 | 104 | if (e) e.preventDefault() 105 | 106 | if (!$parent.length) { 107 | $parent = $this.hasClass('alert') ? $this : $this.parent() 108 | } 109 | 110 | $parent.trigger(e = $.Event('close.bs.alert')) 111 | 112 | if (e.isDefaultPrevented()) return 113 | 114 | $parent.removeClass('in') 115 | 116 | function removeElement() { 117 | // detach from parent, fire event then clean up data 118 | $parent.detach().trigger('closed.bs.alert').remove() 119 | } 120 | 121 | $.support.transition && $parent.hasClass('fade') ? 122 | $parent 123 | .one('bsTransitionEnd', removeElement) 124 | .emulateTransitionEnd(150) : 125 | removeElement() 126 | } 127 | 128 | 129 | // ALERT PLUGIN DEFINITION 130 | // ======================= 131 | 132 | function Plugin(option) { 133 | return this.each(function () { 134 | var $this = $(this) 135 | var data = $this.data('bs.alert') 136 | 137 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 138 | if (typeof option == 'string') data[option].call($this) 139 | }) 140 | } 141 | 142 | var old = $.fn.alert 143 | 144 | $.fn.alert = Plugin 145 | $.fn.alert.Constructor = Alert 146 | 147 | 148 | // ALERT NO CONFLICT 149 | // ================= 150 | 151 | $.fn.alert.noConflict = function () { 152 | $.fn.alert = old 153 | return this 154 | } 155 | 156 | 157 | // ALERT DATA-API 158 | // ============== 159 | 160 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 161 | 162 | }(jQuery); 163 | 164 | /* ======================================================================== 165 | * Bootstrap: button.js v3.2.0 166 | * http://getbootstrap.com/javascript/#buttons 167 | * ======================================================================== 168 | * Copyright 2011-2014 Twitter, Inc. 169 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 170 | * ======================================================================== */ 171 | 172 | 173 | +function ($) { 174 | 'use strict'; 175 | 176 | // BUTTON PUBLIC CLASS DEFINITION 177 | // ============================== 178 | 179 | var Button = function (element, options) { 180 | this.$element = $(element) 181 | this.options = $.extend({}, Button.DEFAULTS, options) 182 | this.isLoading = false 183 | } 184 | 185 | Button.VERSION = '3.2.0' 186 | 187 | Button.DEFAULTS = { 188 | loadingText: 'loading...' 189 | } 190 | 191 | Button.prototype.setState = function (state) { 192 | var d = 'disabled' 193 | var $el = this.$element 194 | var val = $el.is('input') ? 'val' : 'html' 195 | var data = $el.data() 196 | 197 | state = state + 'Text' 198 | 199 | if (data.resetText == null) $el.data('resetText', $el[val]()) 200 | 201 | $el[val](data[state] == null ? this.options[state] : data[state]) 202 | 203 | // push to event loop to allow forms to submit 204 | setTimeout($.proxy(function () { 205 | if (state == 'loadingText') { 206 | this.isLoading = true 207 | $el.addClass(d).attr(d, d) 208 | } else if (this.isLoading) { 209 | this.isLoading = false 210 | $el.removeClass(d).removeAttr(d) 211 | } 212 | }, this), 0) 213 | } 214 | 215 | Button.prototype.toggle = function () { 216 | var changed = true 217 | var $parent = this.$element.closest('[data-toggle="buttons"]') 218 | 219 | if ($parent.length) { 220 | var $input = this.$element.find('input') 221 | if ($input.prop('type') == 'radio') { 222 | if ($input.prop('checked') && this.$element.hasClass('active')) changed = false 223 | else $parent.find('.active').removeClass('active') 224 | } 225 | if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') 226 | } 227 | 228 | if (changed) this.$element.toggleClass('active') 229 | } 230 | 231 | 232 | // BUTTON PLUGIN DEFINITION 233 | // ======================== 234 | 235 | function Plugin(option) { 236 | return this.each(function () { 237 | var $this = $(this) 238 | var data = $this.data('bs.button') 239 | var options = typeof option == 'object' && option 240 | 241 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 242 | 243 | if (option == 'toggle') data.toggle() 244 | else if (option) data.setState(option) 245 | }) 246 | } 247 | 248 | var old = $.fn.button 249 | 250 | $.fn.button = Plugin 251 | $.fn.button.Constructor = Button 252 | 253 | 254 | // BUTTON NO CONFLICT 255 | // ================== 256 | 257 | $.fn.button.noConflict = function () { 258 | $.fn.button = old 259 | return this 260 | } 261 | 262 | 263 | // BUTTON DATA-API 264 | // =============== 265 | 266 | $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 267 | var $btn = $(e.target) 268 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 269 | Plugin.call($btn, 'toggle') 270 | e.preventDefault() 271 | }) 272 | 273 | }(jQuery); 274 | 275 | /* ======================================================================== 276 | * Bootstrap: carousel.js v3.2.0 277 | * http://getbootstrap.com/javascript/#carousel 278 | * ======================================================================== 279 | * Copyright 2011-2014 Twitter, Inc. 280 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 281 | * ======================================================================== */ 282 | 283 | 284 | +function ($) { 285 | 'use strict'; 286 | 287 | // CAROUSEL CLASS DEFINITION 288 | // ========================= 289 | 290 | var Carousel = function (element, options) { 291 | this.$element = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this)) 292 | this.$indicators = this.$element.find('.carousel-indicators') 293 | this.options = options 294 | this.paused = 295 | this.sliding = 296 | this.interval = 297 | this.$active = 298 | this.$items = null 299 | 300 | this.options.pause == 'hover' && this.$element 301 | .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 302 | .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 303 | } 304 | 305 | Carousel.VERSION = '3.2.0' 306 | 307 | Carousel.DEFAULTS = { 308 | interval: 5000, 309 | pause: 'hover', 310 | wrap: true 311 | } 312 | 313 | Carousel.prototype.keydown = function (e) { 314 | switch (e.which) { 315 | case 37: this.prev(); break 316 | case 39: this.next(); break 317 | default: return 318 | } 319 | 320 | e.preventDefault() 321 | } 322 | 323 | Carousel.prototype.cycle = function (e) { 324 | e || (this.paused = false) 325 | 326 | this.interval && clearInterval(this.interval) 327 | 328 | this.options.interval 329 | && !this.paused 330 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 331 | 332 | return this 333 | } 334 | 335 | Carousel.prototype.getItemIndex = function (item) { 336 | this.$items = item.parent().children('.item') 337 | return this.$items.index(item || this.$active) 338 | } 339 | 340 | Carousel.prototype.to = function (pos) { 341 | var that = this 342 | var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 343 | 344 | if (pos > (this.$items.length - 1) || pos < 0) return 345 | 346 | if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 347 | if (activeIndex == pos) return this.pause().cycle() 348 | 349 | return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) 350 | } 351 | 352 | Carousel.prototype.pause = function (e) { 353 | e || (this.paused = true) 354 | 355 | if (this.$element.find('.next, .prev').length && $.support.transition) { 356 | this.$element.trigger($.support.transition.end) 357 | this.cycle(true) 358 | } 359 | 360 | this.interval = clearInterval(this.interval) 361 | 362 | return this 363 | } 364 | 365 | Carousel.prototype.next = function () { 366 | if (this.sliding) return 367 | return this.slide('next') 368 | } 369 | 370 | Carousel.prototype.prev = function () { 371 | if (this.sliding) return 372 | return this.slide('prev') 373 | } 374 | 375 | Carousel.prototype.slide = function (type, next) { 376 | var $active = this.$element.find('.item.active') 377 | var $next = next || $active[type]() 378 | var isCycling = this.interval 379 | var direction = type == 'next' ? 'left' : 'right' 380 | var fallback = type == 'next' ? 'first' : 'last' 381 | var that = this 382 | 383 | if (!$next.length) { 384 | if (!this.options.wrap) return 385 | $next = this.$element.find('.item')[fallback]() 386 | } 387 | 388 | if ($next.hasClass('active')) return (this.sliding = false) 389 | 390 | var relatedTarget = $next[0] 391 | var slideEvent = $.Event('slide.bs.carousel', { 392 | relatedTarget: relatedTarget, 393 | direction: direction 394 | }) 395 | this.$element.trigger(slideEvent) 396 | if (slideEvent.isDefaultPrevented()) return 397 | 398 | this.sliding = true 399 | 400 | isCycling && this.pause() 401 | 402 | if (this.$indicators.length) { 403 | this.$indicators.find('.active').removeClass('active') 404 | var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 405 | $nextIndicator && $nextIndicator.addClass('active') 406 | } 407 | 408 | var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" 409 | if ($.support.transition && this.$element.hasClass('slide')) { 410 | $next.addClass(type) 411 | $next[0].offsetWidth // force reflow 412 | $active.addClass(direction) 413 | $next.addClass(direction) 414 | $active 415 | .one('bsTransitionEnd', function () { 416 | $next.removeClass([type, direction].join(' ')).addClass('active') 417 | $active.removeClass(['active', direction].join(' ')) 418 | that.sliding = false 419 | setTimeout(function () { 420 | that.$element.trigger(slidEvent) 421 | }, 0) 422 | }) 423 | .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000) 424 | } else { 425 | $active.removeClass('active') 426 | $next.addClass('active') 427 | this.sliding = false 428 | this.$element.trigger(slidEvent) 429 | } 430 | 431 | isCycling && this.cycle() 432 | 433 | return this 434 | } 435 | 436 | 437 | // CAROUSEL PLUGIN DEFINITION 438 | // ========================== 439 | 440 | function Plugin(option) { 441 | return this.each(function () { 442 | var $this = $(this) 443 | var data = $this.data('bs.carousel') 444 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 445 | var action = typeof option == 'string' ? option : options.slide 446 | 447 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 448 | if (typeof option == 'number') data.to(option) 449 | else if (action) data[action]() 450 | else if (options.interval) data.pause().cycle() 451 | }) 452 | } 453 | 454 | var old = $.fn.carousel 455 | 456 | $.fn.carousel = Plugin 457 | $.fn.carousel.Constructor = Carousel 458 | 459 | 460 | // CAROUSEL NO CONFLICT 461 | // ==================== 462 | 463 | $.fn.carousel.noConflict = function () { 464 | $.fn.carousel = old 465 | return this 466 | } 467 | 468 | 469 | // CAROUSEL DATA-API 470 | // ================= 471 | 472 | $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { 473 | var href 474 | var $this = $(this) 475 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 476 | if (!$target.hasClass('carousel')) return 477 | var options = $.extend({}, $target.data(), $this.data()) 478 | var slideIndex = $this.attr('data-slide-to') 479 | if (slideIndex) options.interval = false 480 | 481 | Plugin.call($target, options) 482 | 483 | if (slideIndex) { 484 | $target.data('bs.carousel').to(slideIndex) 485 | } 486 | 487 | e.preventDefault() 488 | }) 489 | 490 | $(window).on('load', function () { 491 | $('[data-ride="carousel"]').each(function () { 492 | var $carousel = $(this) 493 | Plugin.call($carousel, $carousel.data()) 494 | }) 495 | }) 496 | 497 | }(jQuery); 498 | 499 | /* ======================================================================== 500 | * Bootstrap: collapse.js v3.2.0 501 | * http://getbootstrap.com/javascript/#collapse 502 | * ======================================================================== 503 | * Copyright 2011-2014 Twitter, Inc. 504 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 505 | * ======================================================================== */ 506 | 507 | 508 | +function ($) { 509 | 'use strict'; 510 | 511 | // COLLAPSE PUBLIC CLASS DEFINITION 512 | // ================================ 513 | 514 | var Collapse = function (element, options) { 515 | this.$element = $(element) 516 | this.options = $.extend({}, Collapse.DEFAULTS, options) 517 | this.transitioning = null 518 | 519 | if (this.options.parent) this.$parent = $(this.options.parent) 520 | if (this.options.toggle) this.toggle() 521 | } 522 | 523 | Collapse.VERSION = '3.2.0' 524 | 525 | Collapse.DEFAULTS = { 526 | toggle: true 527 | } 528 | 529 | Collapse.prototype.dimension = function () { 530 | var hasWidth = this.$element.hasClass('width') 531 | return hasWidth ? 'width' : 'height' 532 | } 533 | 534 | Collapse.prototype.show = function () { 535 | if (this.transitioning || this.$element.hasClass('in')) return 536 | 537 | var startEvent = $.Event('show.bs.collapse') 538 | this.$element.trigger(startEvent) 539 | if (startEvent.isDefaultPrevented()) return 540 | 541 | var actives = this.$parent && this.$parent.find('> .panel > .in') 542 | 543 | if (actives && actives.length) { 544 | var hasData = actives.data('bs.collapse') 545 | if (hasData && hasData.transitioning) return 546 | Plugin.call(actives, 'hide') 547 | hasData || actives.data('bs.collapse', null) 548 | } 549 | 550 | var dimension = this.dimension() 551 | 552 | this.$element 553 | .removeClass('collapse') 554 | .addClass('collapsing')[dimension](0) 555 | 556 | this.transitioning = 1 557 | 558 | var complete = function () { 559 | this.$element 560 | .removeClass('collapsing') 561 | .addClass('collapse in')[dimension]('') 562 | this.transitioning = 0 563 | this.$element 564 | .trigger('shown.bs.collapse') 565 | } 566 | 567 | if (!$.support.transition) return complete.call(this) 568 | 569 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 570 | 571 | this.$element 572 | .one('bsTransitionEnd', $.proxy(complete, this)) 573 | .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize]) 574 | } 575 | 576 | Collapse.prototype.hide = function () { 577 | if (this.transitioning || !this.$element.hasClass('in')) return 578 | 579 | var startEvent = $.Event('hide.bs.collapse') 580 | this.$element.trigger(startEvent) 581 | if (startEvent.isDefaultPrevented()) return 582 | 583 | var dimension = this.dimension() 584 | 585 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight 586 | 587 | this.$element 588 | .addClass('collapsing') 589 | .removeClass('collapse') 590 | .removeClass('in') 591 | 592 | this.transitioning = 1 593 | 594 | var complete = function () { 595 | this.transitioning = 0 596 | this.$element 597 | .trigger('hidden.bs.collapse') 598 | .removeClass('collapsing') 599 | .addClass('collapse') 600 | } 601 | 602 | if (!$.support.transition) return complete.call(this) 603 | 604 | this.$element 605 | [dimension](0) 606 | .one('bsTransitionEnd', $.proxy(complete, this)) 607 | .emulateTransitionEnd(350) 608 | } 609 | 610 | Collapse.prototype.toggle = function () { 611 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 612 | } 613 | 614 | 615 | // COLLAPSE PLUGIN DEFINITION 616 | // ========================== 617 | 618 | function Plugin(option) { 619 | return this.each(function () { 620 | var $this = $(this) 621 | var data = $this.data('bs.collapse') 622 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 623 | 624 | if (!data && options.toggle && option == 'show') option = !option 625 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 626 | if (typeof option == 'string') data[option]() 627 | }) 628 | } 629 | 630 | var old = $.fn.collapse 631 | 632 | $.fn.collapse = Plugin 633 | $.fn.collapse.Constructor = Collapse 634 | 635 | 636 | // COLLAPSE NO CONFLICT 637 | // ==================== 638 | 639 | $.fn.collapse.noConflict = function () { 640 | $.fn.collapse = old 641 | return this 642 | } 643 | 644 | 645 | // COLLAPSE DATA-API 646 | // ================= 647 | 648 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 649 | var href 650 | var $this = $(this) 651 | var target = $this.attr('data-target') 652 | || e.preventDefault() 653 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 654 | var $target = $(target) 655 | var data = $target.data('bs.collapse') 656 | var option = data ? 'toggle' : $this.data() 657 | var parent = $this.attr('data-parent') 658 | var $parent = parent && $(parent) 659 | 660 | if (!data || !data.transitioning) { 661 | if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed') 662 | $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') 663 | } 664 | 665 | Plugin.call($target, option) 666 | }) 667 | 668 | }(jQuery); 669 | 670 | /* ======================================================================== 671 | * Bootstrap: dropdown.js v3.2.0 672 | * http://getbootstrap.com/javascript/#dropdowns 673 | * ======================================================================== 674 | * Copyright 2011-2014 Twitter, Inc. 675 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 676 | * ======================================================================== */ 677 | 678 | 679 | +function ($) { 680 | 'use strict'; 681 | 682 | // DROPDOWN CLASS DEFINITION 683 | // ========================= 684 | 685 | var backdrop = '.dropdown-backdrop' 686 | var toggle = '[data-toggle="dropdown"]' 687 | var Dropdown = function (element) { 688 | $(element).on('click.bs.dropdown', this.toggle) 689 | } 690 | 691 | Dropdown.VERSION = '3.2.0' 692 | 693 | Dropdown.prototype.toggle = function (e) { 694 | var $this = $(this) 695 | 696 | if ($this.is('.disabled, :disabled')) return 697 | 698 | var $parent = getParent($this) 699 | var isActive = $parent.hasClass('open') 700 | 701 | clearMenus() 702 | 703 | if (!isActive) { 704 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 705 | // if mobile we use a backdrop because click events don't delegate 706 | $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus) 707 | } 708 | 709 | var relatedTarget = { relatedTarget: this } 710 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 711 | 712 | if (e.isDefaultPrevented()) return 713 | 714 | $this.trigger('focus') 715 | 716 | $parent 717 | .toggleClass('open') 718 | .trigger('shown.bs.dropdown', relatedTarget) 719 | } 720 | 721 | return false 722 | } 723 | 724 | Dropdown.prototype.keydown = function (e) { 725 | if (!/(38|40|27)/.test(e.keyCode)) return 726 | 727 | var $this = $(this) 728 | 729 | e.preventDefault() 730 | e.stopPropagation() 731 | 732 | if ($this.is('.disabled, :disabled')) return 733 | 734 | var $parent = getParent($this) 735 | var isActive = $parent.hasClass('open') 736 | 737 | if (!isActive || (isActive && e.keyCode == 27)) { 738 | if (e.which == 27) $parent.find(toggle).trigger('focus') 739 | return $this.trigger('click') 740 | } 741 | 742 | var desc = ' li:not(.divider):visible a' 743 | var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc) 744 | 745 | if (!$items.length) return 746 | 747 | var index = $items.index($items.filter(':focus')) 748 | 749 | if (e.keyCode == 38 && index > 0) index-- // up 750 | if (e.keyCode == 40 && index < $items.length - 1) index++ // down 751 | if (!~index) index = 0 752 | 753 | $items.eq(index).trigger('focus') 754 | } 755 | 756 | function clearMenus(e) { 757 | if (e && e.which === 3) return 758 | $(backdrop).remove() 759 | $(toggle).each(function () { 760 | var $parent = getParent($(this)) 761 | var relatedTarget = { relatedTarget: this } 762 | if (!$parent.hasClass('open')) return 763 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 764 | if (e.isDefaultPrevented()) return 765 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) 766 | }) 767 | } 768 | 769 | function getParent($this) { 770 | var selector = $this.attr('data-target') 771 | 772 | if (!selector) { 773 | selector = $this.attr('href') 774 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 775 | } 776 | 777 | var $parent = selector && $(selector) 778 | 779 | return $parent && $parent.length ? $parent : $this.parent() 780 | } 781 | 782 | 783 | // DROPDOWN PLUGIN DEFINITION 784 | // ========================== 785 | 786 | function Plugin(option) { 787 | return this.each(function () { 788 | var $this = $(this) 789 | var data = $this.data('bs.dropdown') 790 | 791 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 792 | if (typeof option == 'string') data[option].call($this) 793 | }) 794 | } 795 | 796 | var old = $.fn.dropdown 797 | 798 | $.fn.dropdown = Plugin 799 | $.fn.dropdown.Constructor = Dropdown 800 | 801 | 802 | // DROPDOWN NO CONFLICT 803 | // ==================== 804 | 805 | $.fn.dropdown.noConflict = function () { 806 | $.fn.dropdown = old 807 | return this 808 | } 809 | 810 | 811 | // APPLY TO STANDARD DROPDOWN ELEMENTS 812 | // =================================== 813 | 814 | $(document) 815 | .on('click.bs.dropdown.data-api', clearMenus) 816 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 817 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) 818 | .on('keydown.bs.dropdown.data-api', toggle + ', [role="menu"], [role="listbox"]', Dropdown.prototype.keydown) 819 | 820 | }(jQuery); 821 | 822 | /* ======================================================================== 823 | * Bootstrap: modal.js v3.2.0 824 | * http://getbootstrap.com/javascript/#modals 825 | * ======================================================================== 826 | * Copyright 2011-2014 Twitter, Inc. 827 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 828 | * ======================================================================== */ 829 | 830 | 831 | +function ($) { 832 | 'use strict'; 833 | 834 | // MODAL CLASS DEFINITION 835 | // ====================== 836 | 837 | var Modal = function (element, options) { 838 | this.options = options 839 | this.$body = $(document.body) 840 | this.$element = $(element) 841 | this.$backdrop = 842 | this.isShown = null 843 | this.scrollbarWidth = 0 844 | 845 | if (this.options.remote) { 846 | this.$element 847 | .find('.modal-content') 848 | .load(this.options.remote, $.proxy(function () { 849 | this.$element.trigger('loaded.bs.modal') 850 | }, this)) 851 | } 852 | } 853 | 854 | Modal.VERSION = '3.2.0' 855 | 856 | Modal.DEFAULTS = { 857 | backdrop: true, 858 | keyboard: true, 859 | show: true 860 | } 861 | 862 | Modal.prototype.toggle = function (_relatedTarget) { 863 | return this.isShown ? this.hide() : this.show(_relatedTarget) 864 | } 865 | 866 | Modal.prototype.show = function (_relatedTarget) { 867 | var that = this 868 | var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 869 | 870 | this.$element.trigger(e) 871 | 872 | if (this.isShown || e.isDefaultPrevented()) return 873 | 874 | this.isShown = true 875 | 876 | this.checkScrollbar() 877 | this.$body.addClass('modal-open') 878 | 879 | this.setScrollbar() 880 | this.escape() 881 | 882 | this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 883 | 884 | this.backdrop(function () { 885 | var transition = $.support.transition && that.$element.hasClass('fade') 886 | 887 | if (!that.$element.parent().length) { 888 | that.$element.appendTo(that.$body) // don't move modals dom position 889 | } 890 | 891 | that.$element 892 | .show() 893 | .scrollTop(0) 894 | 895 | if (transition) { 896 | that.$element[0].offsetWidth // force reflow 897 | } 898 | 899 | that.$element 900 | .addClass('in') 901 | .attr('aria-hidden', false) 902 | 903 | that.enforceFocus() 904 | 905 | var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 906 | 907 | transition ? 908 | that.$element.find('.modal-dialog') // wait for modal to slide in 909 | .one('bsTransitionEnd', function () { 910 | that.$element.trigger('focus').trigger(e) 911 | }) 912 | .emulateTransitionEnd(300) : 913 | that.$element.trigger('focus').trigger(e) 914 | }) 915 | } 916 | 917 | Modal.prototype.hide = function (e) { 918 | if (e) e.preventDefault() 919 | 920 | e = $.Event('hide.bs.modal') 921 | 922 | this.$element.trigger(e) 923 | 924 | if (!this.isShown || e.isDefaultPrevented()) return 925 | 926 | this.isShown = false 927 | 928 | this.$body.removeClass('modal-open') 929 | 930 | this.resetScrollbar() 931 | this.escape() 932 | 933 | $(document).off('focusin.bs.modal') 934 | 935 | this.$element 936 | .removeClass('in') 937 | .attr('aria-hidden', true) 938 | .off('click.dismiss.bs.modal') 939 | 940 | $.support.transition && this.$element.hasClass('fade') ? 941 | this.$element 942 | .one('bsTransitionEnd', $.proxy(this.hideModal, this)) 943 | .emulateTransitionEnd(300) : 944 | this.hideModal() 945 | } 946 | 947 | Modal.prototype.enforceFocus = function () { 948 | $(document) 949 | .off('focusin.bs.modal') // guard against infinite focus loop 950 | .on('focusin.bs.modal', $.proxy(function (e) { 951 | if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { 952 | this.$element.trigger('focus') 953 | } 954 | }, this)) 955 | } 956 | 957 | Modal.prototype.escape = function () { 958 | if (this.isShown && this.options.keyboard) { 959 | this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) { 960 | e.which == 27 && this.hide() 961 | }, this)) 962 | } else if (!this.isShown) { 963 | this.$element.off('keyup.dismiss.bs.modal') 964 | } 965 | } 966 | 967 | Modal.prototype.hideModal = function () { 968 | var that = this 969 | this.$element.hide() 970 | this.backdrop(function () { 971 | that.$element.trigger('hidden.bs.modal') 972 | }) 973 | } 974 | 975 | Modal.prototype.removeBackdrop = function () { 976 | this.$backdrop && this.$backdrop.remove() 977 | this.$backdrop = null 978 | } 979 | 980 | Modal.prototype.backdrop = function (callback) { 981 | var that = this 982 | var animate = this.$element.hasClass('fade') ? 'fade' : '' 983 | 984 | if (this.isShown && this.options.backdrop) { 985 | var doAnimate = $.support.transition && animate 986 | 987 | this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') 988 | .appendTo(this.$body) 989 | 990 | this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 991 | if (e.target !== e.currentTarget) return 992 | this.options.backdrop == 'static' 993 | ? this.$element[0].focus.call(this.$element[0]) 994 | : this.hide.call(this) 995 | }, this)) 996 | 997 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 998 | 999 | this.$backdrop.addClass('in') 1000 | 1001 | if (!callback) return 1002 | 1003 | doAnimate ? 1004 | this.$backdrop 1005 | .one('bsTransitionEnd', callback) 1006 | .emulateTransitionEnd(150) : 1007 | callback() 1008 | 1009 | } else if (!this.isShown && this.$backdrop) { 1010 | this.$backdrop.removeClass('in') 1011 | 1012 | var callbackRemove = function () { 1013 | that.removeBackdrop() 1014 | callback && callback() 1015 | } 1016 | $.support.transition && this.$element.hasClass('fade') ? 1017 | this.$backdrop 1018 | .one('bsTransitionEnd', callbackRemove) 1019 | .emulateTransitionEnd(150) : 1020 | callbackRemove() 1021 | 1022 | } else if (callback) { 1023 | callback() 1024 | } 1025 | } 1026 | 1027 | Modal.prototype.checkScrollbar = function () { 1028 | if (document.body.clientWidth >= window.innerWidth) return 1029 | this.scrollbarWidth = this.scrollbarWidth || this.measureScrollbar() 1030 | } 1031 | 1032 | Modal.prototype.setScrollbar = function () { 1033 | var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) 1034 | if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) 1035 | } 1036 | 1037 | Modal.prototype.resetScrollbar = function () { 1038 | this.$body.css('padding-right', '') 1039 | } 1040 | 1041 | Modal.prototype.measureScrollbar = function () { // thx walsh 1042 | var scrollDiv = document.createElement('div') 1043 | scrollDiv.className = 'modal-scrollbar-measure' 1044 | this.$body.append(scrollDiv) 1045 | var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 1046 | this.$body[0].removeChild(scrollDiv) 1047 | return scrollbarWidth 1048 | } 1049 | 1050 | 1051 | // MODAL PLUGIN DEFINITION 1052 | // ======================= 1053 | 1054 | function Plugin(option, _relatedTarget) { 1055 | return this.each(function () { 1056 | var $this = $(this) 1057 | var data = $this.data('bs.modal') 1058 | var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) 1059 | 1060 | if (!data) $this.data('bs.modal', (data = new Modal(this, options))) 1061 | if (typeof option == 'string') data[option](_relatedTarget) 1062 | else if (options.show) data.show(_relatedTarget) 1063 | }) 1064 | } 1065 | 1066 | var old = $.fn.modal 1067 | 1068 | $.fn.modal = Plugin 1069 | $.fn.modal.Constructor = Modal 1070 | 1071 | 1072 | // MODAL NO CONFLICT 1073 | // ================= 1074 | 1075 | $.fn.modal.noConflict = function () { 1076 | $.fn.modal = old 1077 | return this 1078 | } 1079 | 1080 | 1081 | // MODAL DATA-API 1082 | // ============== 1083 | 1084 | $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { 1085 | var $this = $(this) 1086 | var href = $this.attr('href') 1087 | var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 1088 | var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) 1089 | 1090 | if ($this.is('a')) e.preventDefault() 1091 | 1092 | $target.one('show.bs.modal', function (showEvent) { 1093 | if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown 1094 | $target.one('hidden.bs.modal', function () { 1095 | $this.is(':visible') && $this.trigger('focus') 1096 | }) 1097 | }) 1098 | Plugin.call($target, option, this) 1099 | }) 1100 | 1101 | }(jQuery); 1102 | 1103 | /* ======================================================================== 1104 | * Bootstrap: tooltip.js v3.2.0 1105 | * http://getbootstrap.com/javascript/#tooltip 1106 | * Inspired by the original jQuery.tipsy by Jason Frame 1107 | * ======================================================================== 1108 | * Copyright 2011-2014 Twitter, Inc. 1109 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1110 | * ======================================================================== */ 1111 | 1112 | 1113 | +function ($) { 1114 | 'use strict'; 1115 | 1116 | // TOOLTIP PUBLIC CLASS DEFINITION 1117 | // =============================== 1118 | 1119 | var Tooltip = function (element, options) { 1120 | this.type = 1121 | this.options = 1122 | this.enabled = 1123 | this.timeout = 1124 | this.hoverState = 1125 | this.$element = null 1126 | 1127 | this.init('tooltip', element, options) 1128 | } 1129 | 1130 | Tooltip.VERSION = '3.2.0' 1131 | 1132 | Tooltip.DEFAULTS = { 1133 | animation: true, 1134 | placement: 'top', 1135 | selector: false, 1136 | template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', 1137 | trigger: 'hover focus', 1138 | title: '', 1139 | delay: 0, 1140 | html: false, 1141 | container: false, 1142 | viewport: { 1143 | selector: 'body', 1144 | padding: 0 1145 | } 1146 | } 1147 | 1148 | Tooltip.prototype.init = function (type, element, options) { 1149 | this.enabled = true 1150 | this.type = type 1151 | this.$element = $(element) 1152 | this.options = this.getOptions(options) 1153 | this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport) 1154 | 1155 | var triggers = this.options.trigger.split(' ') 1156 | 1157 | for (var i = triggers.length; i--;) { 1158 | var trigger = triggers[i] 1159 | 1160 | if (trigger == 'click') { 1161 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) 1162 | } else if (trigger != 'manual') { 1163 | var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' 1164 | var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' 1165 | 1166 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) 1167 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) 1168 | } 1169 | } 1170 | 1171 | this.options.selector ? 1172 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 1173 | this.fixTitle() 1174 | } 1175 | 1176 | Tooltip.prototype.getDefaults = function () { 1177 | return Tooltip.DEFAULTS 1178 | } 1179 | 1180 | Tooltip.prototype.getOptions = function (options) { 1181 | options = $.extend({}, this.getDefaults(), this.$element.data(), options) 1182 | 1183 | if (options.delay && typeof options.delay == 'number') { 1184 | options.delay = { 1185 | show: options.delay, 1186 | hide: options.delay 1187 | } 1188 | } 1189 | 1190 | return options 1191 | } 1192 | 1193 | Tooltip.prototype.getDelegateOptions = function () { 1194 | var options = {} 1195 | var defaults = this.getDefaults() 1196 | 1197 | this._options && $.each(this._options, function (key, value) { 1198 | if (defaults[key] != value) options[key] = value 1199 | }) 1200 | 1201 | return options 1202 | } 1203 | 1204 | Tooltip.prototype.enter = function (obj) { 1205 | var self = obj instanceof this.constructor ? 1206 | obj : $(obj.currentTarget).data('bs.' + this.type) 1207 | 1208 | if (!self) { 1209 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1210 | $(obj.currentTarget).data('bs.' + this.type, self) 1211 | } 1212 | 1213 | clearTimeout(self.timeout) 1214 | 1215 | self.hoverState = 'in' 1216 | 1217 | if (!self.options.delay || !self.options.delay.show) return self.show() 1218 | 1219 | self.timeout = setTimeout(function () { 1220 | if (self.hoverState == 'in') self.show() 1221 | }, self.options.delay.show) 1222 | } 1223 | 1224 | Tooltip.prototype.leave = function (obj) { 1225 | var self = obj instanceof this.constructor ? 1226 | obj : $(obj.currentTarget).data('bs.' + this.type) 1227 | 1228 | if (!self) { 1229 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1230 | $(obj.currentTarget).data('bs.' + this.type, self) 1231 | } 1232 | 1233 | clearTimeout(self.timeout) 1234 | 1235 | self.hoverState = 'out' 1236 | 1237 | if (!self.options.delay || !self.options.delay.hide) return self.hide() 1238 | 1239 | self.timeout = setTimeout(function () { 1240 | if (self.hoverState == 'out') self.hide() 1241 | }, self.options.delay.hide) 1242 | } 1243 | 1244 | Tooltip.prototype.show = function () { 1245 | var e = $.Event('show.bs.' + this.type) 1246 | 1247 | if (this.hasContent() && this.enabled) { 1248 | this.$element.trigger(e) 1249 | 1250 | var inDom = $.contains(document.documentElement, this.$element[0]) 1251 | if (e.isDefaultPrevented() || !inDom) return 1252 | var that = this 1253 | 1254 | var $tip = this.tip() 1255 | 1256 | var tipId = this.getUID(this.type) 1257 | 1258 | this.setContent() 1259 | $tip.attr('id', tipId) 1260 | this.$element.attr('aria-describedby', tipId) 1261 | 1262 | if (this.options.animation) $tip.addClass('fade') 1263 | 1264 | var placement = typeof this.options.placement == 'function' ? 1265 | this.options.placement.call(this, $tip[0], this.$element[0]) : 1266 | this.options.placement 1267 | 1268 | var autoToken = /\s?auto?\s?/i 1269 | var autoPlace = autoToken.test(placement) 1270 | if (autoPlace) placement = placement.replace(autoToken, '') || 'top' 1271 | 1272 | $tip 1273 | .detach() 1274 | .css({ top: 0, left: 0, display: 'block' }) 1275 | .addClass(placement) 1276 | .data('bs.' + this.type, this) 1277 | 1278 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) 1279 | 1280 | var pos = this.getPosition() 1281 | var actualWidth = $tip[0].offsetWidth 1282 | var actualHeight = $tip[0].offsetHeight 1283 | 1284 | if (autoPlace) { 1285 | var orgPlacement = placement 1286 | var $parent = this.$element.parent() 1287 | var parentDim = this.getPosition($parent) 1288 | 1289 | placement = placement == 'bottom' && pos.top + pos.height + actualHeight - parentDim.scroll > parentDim.height ? 'top' : 1290 | placement == 'top' && pos.top - parentDim.scroll - actualHeight < 0 ? 'bottom' : 1291 | placement == 'right' && pos.right + actualWidth > parentDim.width ? 'left' : 1292 | placement == 'left' && pos.left - actualWidth < parentDim.left ? 'right' : 1293 | placement 1294 | 1295 | $tip 1296 | .removeClass(orgPlacement) 1297 | .addClass(placement) 1298 | } 1299 | 1300 | var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) 1301 | 1302 | this.applyPlacement(calculatedOffset, placement) 1303 | 1304 | var complete = function () { 1305 | that.$element.trigger('shown.bs.' + that.type) 1306 | that.hoverState = null 1307 | } 1308 | 1309 | $.support.transition && this.$tip.hasClass('fade') ? 1310 | $tip 1311 | .one('bsTransitionEnd', complete) 1312 | .emulateTransitionEnd(150) : 1313 | complete() 1314 | } 1315 | } 1316 | 1317 | Tooltip.prototype.applyPlacement = function (offset, placement) { 1318 | var $tip = this.tip() 1319 | var width = $tip[0].offsetWidth 1320 | var height = $tip[0].offsetHeight 1321 | 1322 | // manually read margins because getBoundingClientRect includes difference 1323 | var marginTop = parseInt($tip.css('margin-top'), 10) 1324 | var marginLeft = parseInt($tip.css('margin-left'), 10) 1325 | 1326 | // we must check for NaN for ie 8/9 1327 | if (isNaN(marginTop)) marginTop = 0 1328 | if (isNaN(marginLeft)) marginLeft = 0 1329 | 1330 | offset.top = offset.top + marginTop 1331 | offset.left = offset.left + marginLeft 1332 | 1333 | // $.fn.offset doesn't round pixel values 1334 | // so we use setOffset directly with our own function B-0 1335 | $.offset.setOffset($tip[0], $.extend({ 1336 | using: function (props) { 1337 | $tip.css({ 1338 | top: Math.round(props.top), 1339 | left: Math.round(props.left) 1340 | }) 1341 | } 1342 | }, offset), 0) 1343 | 1344 | $tip.addClass('in') 1345 | 1346 | // check to see if placing tip in new offset caused the tip to resize itself 1347 | var actualWidth = $tip[0].offsetWidth 1348 | var actualHeight = $tip[0].offsetHeight 1349 | 1350 | if (placement == 'top' && actualHeight != height) { 1351 | offset.top = offset.top + height - actualHeight 1352 | } 1353 | 1354 | var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) 1355 | 1356 | if (delta.left) offset.left += delta.left 1357 | else offset.top += delta.top 1358 | 1359 | var arrowDelta = delta.left ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight 1360 | var arrowPosition = delta.left ? 'left' : 'top' 1361 | var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight' 1362 | 1363 | $tip.offset(offset) 1364 | this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition) 1365 | } 1366 | 1367 | Tooltip.prototype.replaceArrow = function (delta, dimension, position) { 1368 | this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '') 1369 | } 1370 | 1371 | Tooltip.prototype.setContent = function () { 1372 | var $tip = this.tip() 1373 | var title = this.getTitle() 1374 | 1375 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) 1376 | $tip.removeClass('fade in top bottom left right') 1377 | } 1378 | 1379 | Tooltip.prototype.hide = function () { 1380 | var that = this 1381 | var $tip = this.tip() 1382 | var e = $.Event('hide.bs.' + this.type) 1383 | 1384 | this.$element.removeAttr('aria-describedby') 1385 | 1386 | function complete() { 1387 | if (that.hoverState != 'in') $tip.detach() 1388 | that.$element.trigger('hidden.bs.' + that.type) 1389 | } 1390 | 1391 | this.$element.trigger(e) 1392 | 1393 | if (e.isDefaultPrevented()) return 1394 | 1395 | $tip.removeClass('in') 1396 | 1397 | $.support.transition && this.$tip.hasClass('fade') ? 1398 | $tip 1399 | .one('bsTransitionEnd', complete) 1400 | .emulateTransitionEnd(150) : 1401 | complete() 1402 | 1403 | this.hoverState = null 1404 | 1405 | return this 1406 | } 1407 | 1408 | Tooltip.prototype.fixTitle = function () { 1409 | var $e = this.$element 1410 | if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') { 1411 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') 1412 | } 1413 | } 1414 | 1415 | Tooltip.prototype.hasContent = function () { 1416 | return this.getTitle() 1417 | } 1418 | 1419 | Tooltip.prototype.getPosition = function ($element) { 1420 | $element = $element || this.$element 1421 | var el = $element[0] 1422 | var isBody = el.tagName == 'BODY' 1423 | return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : null, { 1424 | scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(), 1425 | width: isBody ? $(window).width() : $element.outerWidth(), 1426 | height: isBody ? $(window).height() : $element.outerHeight() 1427 | }, isBody ? { top: 0, left: 0 } : $element.offset()) 1428 | } 1429 | 1430 | Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { 1431 | return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1432 | placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1433 | placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : 1434 | /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } 1435 | 1436 | } 1437 | 1438 | Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { 1439 | var delta = { top: 0, left: 0 } 1440 | if (!this.$viewport) return delta 1441 | 1442 | var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 1443 | var viewportDimensions = this.getPosition(this.$viewport) 1444 | 1445 | if (/right|left/.test(placement)) { 1446 | var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll 1447 | var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight 1448 | if (topEdgeOffset < viewportDimensions.top) { // top overflow 1449 | delta.top = viewportDimensions.top - topEdgeOffset 1450 | } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow 1451 | delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset 1452 | } 1453 | } else { 1454 | var leftEdgeOffset = pos.left - viewportPadding 1455 | var rightEdgeOffset = pos.left + viewportPadding + actualWidth 1456 | if (leftEdgeOffset < viewportDimensions.left) { // left overflow 1457 | delta.left = viewportDimensions.left - leftEdgeOffset 1458 | } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow 1459 | delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset 1460 | } 1461 | } 1462 | 1463 | return delta 1464 | } 1465 | 1466 | Tooltip.prototype.getTitle = function () { 1467 | var title 1468 | var $e = this.$element 1469 | var o = this.options 1470 | 1471 | title = $e.attr('data-original-title') 1472 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 1473 | 1474 | return title 1475 | } 1476 | 1477 | Tooltip.prototype.getUID = function (prefix) { 1478 | do prefix += ~~(Math.random() * 1000000) 1479 | while (document.getElementById(prefix)) 1480 | return prefix 1481 | } 1482 | 1483 | Tooltip.prototype.tip = function () { 1484 | return (this.$tip = this.$tip || $(this.options.template)) 1485 | } 1486 | 1487 | Tooltip.prototype.arrow = function () { 1488 | return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) 1489 | } 1490 | 1491 | Tooltip.prototype.validate = function () { 1492 | if (!this.$element[0].parentNode) { 1493 | this.hide() 1494 | this.$element = null 1495 | this.options = null 1496 | } 1497 | } 1498 | 1499 | Tooltip.prototype.enable = function () { 1500 | this.enabled = true 1501 | } 1502 | 1503 | Tooltip.prototype.disable = function () { 1504 | this.enabled = false 1505 | } 1506 | 1507 | Tooltip.prototype.toggleEnabled = function () { 1508 | this.enabled = !this.enabled 1509 | } 1510 | 1511 | Tooltip.prototype.toggle = function (e) { 1512 | var self = this 1513 | if (e) { 1514 | self = $(e.currentTarget).data('bs.' + this.type) 1515 | if (!self) { 1516 | self = new this.constructor(e.currentTarget, this.getDelegateOptions()) 1517 | $(e.currentTarget).data('bs.' + this.type, self) 1518 | } 1519 | } 1520 | 1521 | self.tip().hasClass('in') ? self.leave(self) : self.enter(self) 1522 | } 1523 | 1524 | Tooltip.prototype.destroy = function () { 1525 | clearTimeout(this.timeout) 1526 | this.hide().$element.off('.' + this.type).removeData('bs.' + this.type) 1527 | } 1528 | 1529 | 1530 | // TOOLTIP PLUGIN DEFINITION 1531 | // ========================= 1532 | 1533 | function Plugin(option) { 1534 | return this.each(function () { 1535 | var $this = $(this) 1536 | var data = $this.data('bs.tooltip') 1537 | var options = typeof option == 'object' && option 1538 | 1539 | if (!data && option == 'destroy') return 1540 | if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) 1541 | if (typeof option == 'string') data[option]() 1542 | }) 1543 | } 1544 | 1545 | var old = $.fn.tooltip 1546 | 1547 | $.fn.tooltip = Plugin 1548 | $.fn.tooltip.Constructor = Tooltip 1549 | 1550 | 1551 | // TOOLTIP NO CONFLICT 1552 | // =================== 1553 | 1554 | $.fn.tooltip.noConflict = function () { 1555 | $.fn.tooltip = old 1556 | return this 1557 | } 1558 | 1559 | }(jQuery); 1560 | 1561 | /* ======================================================================== 1562 | * Bootstrap: popover.js v3.2.0 1563 | * http://getbootstrap.com/javascript/#popovers 1564 | * ======================================================================== 1565 | * Copyright 2011-2014 Twitter, Inc. 1566 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1567 | * ======================================================================== */ 1568 | 1569 | 1570 | +function ($) { 1571 | 'use strict'; 1572 | 1573 | // POPOVER PUBLIC CLASS DEFINITION 1574 | // =============================== 1575 | 1576 | var Popover = function (element, options) { 1577 | this.init('popover', element, options) 1578 | } 1579 | 1580 | if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') 1581 | 1582 | Popover.VERSION = '3.2.0' 1583 | 1584 | Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { 1585 | placement: 'right', 1586 | trigger: 'click', 1587 | content: '', 1588 | template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' 1589 | }) 1590 | 1591 | 1592 | // NOTE: POPOVER EXTENDS tooltip.js 1593 | // ================================ 1594 | 1595 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) 1596 | 1597 | Popover.prototype.constructor = Popover 1598 | 1599 | Popover.prototype.getDefaults = function () { 1600 | return Popover.DEFAULTS 1601 | } 1602 | 1603 | Popover.prototype.setContent = function () { 1604 | var $tip = this.tip() 1605 | var title = this.getTitle() 1606 | var content = this.getContent() 1607 | 1608 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 1609 | $tip.find('.popover-content').empty()[ // we use append for html objects to maintain js events 1610 | this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' 1611 | ](content) 1612 | 1613 | $tip.removeClass('fade top bottom left right in') 1614 | 1615 | // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do 1616 | // this manually by checking the contents. 1617 | if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() 1618 | } 1619 | 1620 | Popover.prototype.hasContent = function () { 1621 | return this.getTitle() || this.getContent() 1622 | } 1623 | 1624 | Popover.prototype.getContent = function () { 1625 | var $e = this.$element 1626 | var o = this.options 1627 | 1628 | return $e.attr('data-content') 1629 | || (typeof o.content == 'function' ? 1630 | o.content.call($e[0]) : 1631 | o.content) 1632 | } 1633 | 1634 | Popover.prototype.arrow = function () { 1635 | return (this.$arrow = this.$arrow || this.tip().find('.arrow')) 1636 | } 1637 | 1638 | Popover.prototype.tip = function () { 1639 | if (!this.$tip) this.$tip = $(this.options.template) 1640 | return this.$tip 1641 | } 1642 | 1643 | 1644 | // POPOVER PLUGIN DEFINITION 1645 | // ========================= 1646 | 1647 | function Plugin(option) { 1648 | return this.each(function () { 1649 | var $this = $(this) 1650 | var data = $this.data('bs.popover') 1651 | var options = typeof option == 'object' && option 1652 | 1653 | if (!data && option == 'destroy') return 1654 | if (!data) $this.data('bs.popover', (data = new Popover(this, options))) 1655 | if (typeof option == 'string') data[option]() 1656 | }) 1657 | } 1658 | 1659 | var old = $.fn.popover 1660 | 1661 | $.fn.popover = Plugin 1662 | $.fn.popover.Constructor = Popover 1663 | 1664 | 1665 | // POPOVER NO CONFLICT 1666 | // =================== 1667 | 1668 | $.fn.popover.noConflict = function () { 1669 | $.fn.popover = old 1670 | return this 1671 | } 1672 | 1673 | }(jQuery); 1674 | 1675 | /* ======================================================================== 1676 | * Bootstrap: scrollspy.js v3.2.0 1677 | * http://getbootstrap.com/javascript/#scrollspy 1678 | * ======================================================================== 1679 | * Copyright 2011-2014 Twitter, Inc. 1680 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1681 | * ======================================================================== */ 1682 | 1683 | 1684 | +function ($) { 1685 | 'use strict'; 1686 | 1687 | // SCROLLSPY CLASS DEFINITION 1688 | // ========================== 1689 | 1690 | function ScrollSpy(element, options) { 1691 | var process = $.proxy(this.process, this) 1692 | 1693 | this.$body = $('body') 1694 | this.$scrollElement = $(element).is('body') ? $(window) : $(element) 1695 | this.options = $.extend({}, ScrollSpy.DEFAULTS, options) 1696 | this.selector = (this.options.target || '') + ' .nav li > a' 1697 | this.offsets = [] 1698 | this.targets = [] 1699 | this.activeTarget = null 1700 | this.scrollHeight = 0 1701 | 1702 | this.$scrollElement.on('scroll.bs.scrollspy', process) 1703 | this.refresh() 1704 | this.process() 1705 | } 1706 | 1707 | ScrollSpy.VERSION = '3.2.0' 1708 | 1709 | ScrollSpy.DEFAULTS = { 1710 | offset: 10 1711 | } 1712 | 1713 | ScrollSpy.prototype.getScrollHeight = function () { 1714 | return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) 1715 | } 1716 | 1717 | ScrollSpy.prototype.refresh = function () { 1718 | var offsetMethod = 'offset' 1719 | var offsetBase = 0 1720 | 1721 | if (!$.isWindow(this.$scrollElement[0])) { 1722 | offsetMethod = 'position' 1723 | offsetBase = this.$scrollElement.scrollTop() 1724 | } 1725 | 1726 | this.offsets = [] 1727 | this.targets = [] 1728 | this.scrollHeight = this.getScrollHeight() 1729 | 1730 | var self = this 1731 | 1732 | this.$body 1733 | .find(this.selector) 1734 | .map(function () { 1735 | var $el = $(this) 1736 | var href = $el.data('target') || $el.attr('href') 1737 | var $href = /^#./.test(href) && $(href) 1738 | 1739 | return ($href 1740 | && $href.length 1741 | && $href.is(':visible') 1742 | && [[$href[offsetMethod]().top + offsetBase, href]]) || null 1743 | }) 1744 | .sort(function (a, b) { return a[0] - b[0] }) 1745 | .each(function () { 1746 | self.offsets.push(this[0]) 1747 | self.targets.push(this[1]) 1748 | }) 1749 | } 1750 | 1751 | ScrollSpy.prototype.process = function () { 1752 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 1753 | var scrollHeight = this.getScrollHeight() 1754 | var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() 1755 | var offsets = this.offsets 1756 | var targets = this.targets 1757 | var activeTarget = this.activeTarget 1758 | var i 1759 | 1760 | if (this.scrollHeight != scrollHeight) { 1761 | this.refresh() 1762 | } 1763 | 1764 | if (scrollTop >= maxScroll) { 1765 | return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) 1766 | } 1767 | 1768 | if (activeTarget && scrollTop <= offsets[0]) { 1769 | return activeTarget != (i = targets[0]) && this.activate(i) 1770 | } 1771 | 1772 | for (i = offsets.length; i--;) { 1773 | activeTarget != targets[i] 1774 | && scrollTop >= offsets[i] 1775 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) 1776 | && this.activate(targets[i]) 1777 | } 1778 | } 1779 | 1780 | ScrollSpy.prototype.activate = function (target) { 1781 | this.activeTarget = target 1782 | 1783 | $(this.selector) 1784 | .parentsUntil(this.options.target, '.active') 1785 | .removeClass('active') 1786 | 1787 | var selector = this.selector + 1788 | '[data-target="' + target + '"],' + 1789 | this.selector + '[href="' + target + '"]' 1790 | 1791 | var active = $(selector) 1792 | .parents('li') 1793 | .addClass('active') 1794 | 1795 | if (active.parent('.dropdown-menu').length) { 1796 | active = active 1797 | .closest('li.dropdown') 1798 | .addClass('active') 1799 | } 1800 | 1801 | active.trigger('activate.bs.scrollspy') 1802 | } 1803 | 1804 | 1805 | // SCROLLSPY PLUGIN DEFINITION 1806 | // =========================== 1807 | 1808 | function Plugin(option) { 1809 | return this.each(function () { 1810 | var $this = $(this) 1811 | var data = $this.data('bs.scrollspy') 1812 | var options = typeof option == 'object' && option 1813 | 1814 | if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) 1815 | if (typeof option == 'string') data[option]() 1816 | }) 1817 | } 1818 | 1819 | var old = $.fn.scrollspy 1820 | 1821 | $.fn.scrollspy = Plugin 1822 | $.fn.scrollspy.Constructor = ScrollSpy 1823 | 1824 | 1825 | // SCROLLSPY NO CONFLICT 1826 | // ===================== 1827 | 1828 | $.fn.scrollspy.noConflict = function () { 1829 | $.fn.scrollspy = old 1830 | return this 1831 | } 1832 | 1833 | 1834 | // SCROLLSPY DATA-API 1835 | // ================== 1836 | 1837 | $(window).on('load.bs.scrollspy.data-api', function () { 1838 | $('[data-spy="scroll"]').each(function () { 1839 | var $spy = $(this) 1840 | Plugin.call($spy, $spy.data()) 1841 | }) 1842 | }) 1843 | 1844 | }(jQuery); 1845 | 1846 | /* ======================================================================== 1847 | * Bootstrap: tab.js v3.2.0 1848 | * http://getbootstrap.com/javascript/#tabs 1849 | * ======================================================================== 1850 | * Copyright 2011-2014 Twitter, Inc. 1851 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1852 | * ======================================================================== */ 1853 | 1854 | 1855 | +function ($) { 1856 | 'use strict'; 1857 | 1858 | // TAB CLASS DEFINITION 1859 | // ==================== 1860 | 1861 | var Tab = function (element) { 1862 | this.element = $(element) 1863 | } 1864 | 1865 | Tab.VERSION = '3.2.0' 1866 | 1867 | Tab.prototype.show = function () { 1868 | var $this = this.element 1869 | var $ul = $this.closest('ul:not(.dropdown-menu)') 1870 | var selector = $this.data('target') 1871 | 1872 | if (!selector) { 1873 | selector = $this.attr('href') 1874 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 1875 | } 1876 | 1877 | if ($this.parent('li').hasClass('active')) return 1878 | 1879 | var previous = $ul.find('.active:last a')[0] 1880 | var e = $.Event('show.bs.tab', { 1881 | relatedTarget: previous 1882 | }) 1883 | 1884 | $this.trigger(e) 1885 | 1886 | if (e.isDefaultPrevented()) return 1887 | 1888 | var $target = $(selector) 1889 | 1890 | this.activate($this.closest('li'), $ul) 1891 | this.activate($target, $target.parent(), function () { 1892 | $this.trigger({ 1893 | type: 'shown.bs.tab', 1894 | relatedTarget: previous 1895 | }) 1896 | }) 1897 | } 1898 | 1899 | Tab.prototype.activate = function (element, container, callback) { 1900 | var $active = container.find('> .active') 1901 | var transition = callback 1902 | && $.support.transition 1903 | && $active.hasClass('fade') 1904 | 1905 | function next() { 1906 | $active 1907 | .removeClass('active') 1908 | .find('> .dropdown-menu > .active') 1909 | .removeClass('active') 1910 | 1911 | element.addClass('active') 1912 | 1913 | if (transition) { 1914 | element[0].offsetWidth // reflow for transition 1915 | element.addClass('in') 1916 | } else { 1917 | element.removeClass('fade') 1918 | } 1919 | 1920 | if (element.parent('.dropdown-menu')) { 1921 | element.closest('li.dropdown').addClass('active') 1922 | } 1923 | 1924 | callback && callback() 1925 | } 1926 | 1927 | transition ? 1928 | $active 1929 | .one('bsTransitionEnd', next) 1930 | .emulateTransitionEnd(150) : 1931 | next() 1932 | 1933 | $active.removeClass('in') 1934 | } 1935 | 1936 | 1937 | // TAB PLUGIN DEFINITION 1938 | // ===================== 1939 | 1940 | function Plugin(option) { 1941 | return this.each(function () { 1942 | var $this = $(this) 1943 | var data = $this.data('bs.tab') 1944 | 1945 | if (!data) $this.data('bs.tab', (data = new Tab(this))) 1946 | if (typeof option == 'string') data[option]() 1947 | }) 1948 | } 1949 | 1950 | var old = $.fn.tab 1951 | 1952 | $.fn.tab = Plugin 1953 | $.fn.tab.Constructor = Tab 1954 | 1955 | 1956 | // TAB NO CONFLICT 1957 | // =============== 1958 | 1959 | $.fn.tab.noConflict = function () { 1960 | $.fn.tab = old 1961 | return this 1962 | } 1963 | 1964 | 1965 | // TAB DATA-API 1966 | // ============ 1967 | 1968 | $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 1969 | e.preventDefault() 1970 | Plugin.call($(this), 'show') 1971 | }) 1972 | 1973 | }(jQuery); 1974 | 1975 | /* ======================================================================== 1976 | * Bootstrap: affix.js v3.2.0 1977 | * http://getbootstrap.com/javascript/#affix 1978 | * ======================================================================== 1979 | * Copyright 2011-2014 Twitter, Inc. 1980 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1981 | * ======================================================================== */ 1982 | 1983 | 1984 | +function ($) { 1985 | 'use strict'; 1986 | 1987 | // AFFIX CLASS DEFINITION 1988 | // ====================== 1989 | 1990 | var Affix = function (element, options) { 1991 | this.options = $.extend({}, Affix.DEFAULTS, options) 1992 | 1993 | this.$target = $(this.options.target) 1994 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 1995 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 1996 | 1997 | this.$element = $(element) 1998 | this.affixed = 1999 | this.unpin = 2000 | this.pinnedOffset = null 2001 | 2002 | this.checkPosition() 2003 | } 2004 | 2005 | Affix.VERSION = '3.2.0' 2006 | 2007 | Affix.RESET = 'affix affix-top affix-bottom' 2008 | 2009 | Affix.DEFAULTS = { 2010 | offset: 0, 2011 | target: window 2012 | } 2013 | 2014 | Affix.prototype.getPinnedOffset = function () { 2015 | if (this.pinnedOffset) return this.pinnedOffset 2016 | this.$element.removeClass(Affix.RESET).addClass('affix') 2017 | var scrollTop = this.$target.scrollTop() 2018 | var position = this.$element.offset() 2019 | return (this.pinnedOffset = position.top - scrollTop) 2020 | } 2021 | 2022 | Affix.prototype.checkPositionWithEventLoop = function () { 2023 | setTimeout($.proxy(this.checkPosition, this), 1) 2024 | } 2025 | 2026 | Affix.prototype.checkPosition = function () { 2027 | if (!this.$element.is(':visible')) return 2028 | 2029 | var scrollHeight = $(document).height() 2030 | var scrollTop = this.$target.scrollTop() 2031 | var position = this.$element.offset() 2032 | var offset = this.options.offset 2033 | var offsetTop = offset.top 2034 | var offsetBottom = offset.bottom 2035 | 2036 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 2037 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 2038 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 2039 | 2040 | var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false : 2041 | offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' : 2042 | offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false 2043 | 2044 | if (this.affixed === affix) return 2045 | if (this.unpin != null) this.$element.css('top', '') 2046 | 2047 | var affixType = 'affix' + (affix ? '-' + affix : '') 2048 | var e = $.Event(affixType + '.bs.affix') 2049 | 2050 | this.$element.trigger(e) 2051 | 2052 | if (e.isDefaultPrevented()) return 2053 | 2054 | this.affixed = affix 2055 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 2056 | 2057 | this.$element 2058 | .removeClass(Affix.RESET) 2059 | .addClass(affixType) 2060 | .trigger($.Event(affixType.replace('affix', 'affixed'))) 2061 | 2062 | if (affix == 'bottom') { 2063 | this.$element.offset({ 2064 | top: scrollHeight - this.$element.height() - offsetBottom 2065 | }) 2066 | } 2067 | } 2068 | 2069 | 2070 | // AFFIX PLUGIN DEFINITION 2071 | // ======================= 2072 | 2073 | function Plugin(option) { 2074 | return this.each(function () { 2075 | var $this = $(this) 2076 | var data = $this.data('bs.affix') 2077 | var options = typeof option == 'object' && option 2078 | 2079 | if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 2080 | if (typeof option == 'string') data[option]() 2081 | }) 2082 | } 2083 | 2084 | var old = $.fn.affix 2085 | 2086 | $.fn.affix = Plugin 2087 | $.fn.affix.Constructor = Affix 2088 | 2089 | 2090 | // AFFIX NO CONFLICT 2091 | // ================= 2092 | 2093 | $.fn.affix.noConflict = function () { 2094 | $.fn.affix = old 2095 | return this 2096 | } 2097 | 2098 | 2099 | // AFFIX DATA-API 2100 | // ============== 2101 | 2102 | $(window).on('load', function () { 2103 | $('[data-spy="affix"]').each(function () { 2104 | var $spy = $(this) 2105 | var data = $spy.data() 2106 | 2107 | data.offset = data.offset || {} 2108 | 2109 | if (data.offsetBottom) data.offset.bottom = data.offsetBottom 2110 | if (data.offsetTop) data.offset.top = data.offsetTop 2111 | 2112 | Plugin.call($spy, data) 2113 | }) 2114 | }) 2115 | 2116 | var _TEST_END_FILE2; 2117 | 2118 | }(jQuery); 2119 | -------------------------------------------------------------------------------- /tests/resources/styles-2.css: -------------------------------------------------------------------------------- 1 | html._TEST_INI_FILE2 {font-size: 1em;} 2 | 3 | /*! 4 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 5 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 6 | */ 7 | /* FONT PATH 8 | * -------------------------- */ 9 | @font-face { 10 | font-family: 'FontAwesome'; 11 | src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); 12 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | .fa { 17 | display: inline-block; 18 | font-family: FontAwesome; 19 | font-style: normal; 20 | font-weight: normal; 21 | line-height: 1; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | /* makes the font 33% larger relative to the icon container */ 26 | .fa-lg { 27 | font-size: 1.3333333333333333em; 28 | line-height: 0.75em; 29 | vertical-align: -15%; 30 | } 31 | .fa-2x { 32 | font-size: 2em; 33 | } 34 | .fa-3x { 35 | font-size: 3em; 36 | } 37 | .fa-4x { 38 | font-size: 4em; 39 | } 40 | .fa-5x { 41 | font-size: 5em; 42 | } 43 | .fa-fw { 44 | width: 1.2857142857142858em; 45 | text-align: center; 46 | } 47 | .fa-ul { 48 | padding-left: 0; 49 | margin-left: 2.142857142857143em; 50 | list-style-type: none; 51 | } 52 | .fa-ul > li { 53 | position: relative; 54 | } 55 | .fa-li { 56 | position: absolute; 57 | left: -2.142857142857143em; 58 | width: 2.142857142857143em; 59 | top: 0.14285714285714285em; 60 | text-align: center; 61 | } 62 | .fa-li.fa-lg { 63 | left: -1.8571428571428572em; 64 | } 65 | .fa-border { 66 | padding: .2em .25em .15em; 67 | border: solid 0.08em #eeeeee; 68 | border-radius: .1em; 69 | } 70 | .pull-right { 71 | float: right; 72 | } 73 | .pull-left { 74 | float: left; 75 | } 76 | .fa.pull-left { 77 | margin-right: .3em; 78 | } 79 | .fa.pull-right { 80 | margin-left: .3em; 81 | } 82 | .fa-spin { 83 | -webkit-animation: spin 2s infinite linear; 84 | -moz-animation: spin 2s infinite linear; 85 | -o-animation: spin 2s infinite linear; 86 | animation: spin 2s infinite linear; 87 | } 88 | @-moz-keyframes spin { 89 | 0% { 90 | -moz-transform: rotate(0deg); 91 | } 92 | 100% { 93 | -moz-transform: rotate(359deg); 94 | } 95 | } 96 | @-webkit-keyframes spin { 97 | 0% { 98 | -webkit-transform: rotate(0deg); 99 | } 100 | 100% { 101 | -webkit-transform: rotate(359deg); 102 | } 103 | } 104 | @-o-keyframes spin { 105 | 0% { 106 | -o-transform: rotate(0deg); 107 | } 108 | 100% { 109 | -o-transform: rotate(359deg); 110 | } 111 | } 112 | @-ms-keyframes spin { 113 | 0% { 114 | -ms-transform: rotate(0deg); 115 | } 116 | 100% { 117 | -ms-transform: rotate(359deg); 118 | } 119 | } 120 | @keyframes spin { 121 | 0% { 122 | transform: rotate(0deg); 123 | } 124 | 100% { 125 | transform: rotate(359deg); 126 | } 127 | } 128 | .fa-rotate-90 { 129 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 130 | -webkit-transform: rotate(90deg); 131 | -moz-transform: rotate(90deg); 132 | -ms-transform: rotate(90deg); 133 | -o-transform: rotate(90deg); 134 | transform: rotate(90deg); 135 | } 136 | .fa-rotate-180 { 137 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 138 | -webkit-transform: rotate(180deg); 139 | -moz-transform: rotate(180deg); 140 | -ms-transform: rotate(180deg); 141 | -o-transform: rotate(180deg); 142 | transform: rotate(180deg); 143 | } 144 | .fa-rotate-270 { 145 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 146 | -webkit-transform: rotate(270deg); 147 | -moz-transform: rotate(270deg); 148 | -ms-transform: rotate(270deg); 149 | -o-transform: rotate(270deg); 150 | transform: rotate(270deg); 151 | } 152 | .fa-flip-horizontal { 153 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 154 | -webkit-transform: scale(-1, 1); 155 | -moz-transform: scale(-1, 1); 156 | -ms-transform: scale(-1, 1); 157 | -o-transform: scale(-1, 1); 158 | transform: scale(-1, 1); 159 | } 160 | .fa-flip-vertical { 161 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 162 | -webkit-transform: scale(1, -1); 163 | -moz-transform: scale(1, -1); 164 | -ms-transform: scale(1, -1); 165 | -o-transform: scale(1, -1); 166 | transform: scale(1, -1); 167 | } 168 | .fa-stack { 169 | position: relative; 170 | display: inline-block; 171 | width: 2em; 172 | height: 2em; 173 | line-height: 2em; 174 | vertical-align: middle; 175 | } 176 | .fa-stack-1x, 177 | .fa-stack-2x { 178 | position: absolute; 179 | left: 0; 180 | width: 100%; 181 | text-align: center; 182 | } 183 | .fa-stack-1x { 184 | line-height: inherit; 185 | } 186 | .fa-stack-2x { 187 | font-size: 2em; 188 | } 189 | .fa-inverse { 190 | color: #ffffff; 191 | } 192 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 193 | readers do not read off random characters that represent icons */ 194 | .fa-glass:before { 195 | content: "\f000"; 196 | } 197 | .fa-music:before { 198 | content: "\f001"; 199 | } 200 | .fa-search:before { 201 | content: "\f002"; 202 | } 203 | .fa-envelope-o:before { 204 | content: "\f003"; 205 | } 206 | .fa-heart:before { 207 | content: "\f004"; 208 | } 209 | .fa-star:before { 210 | content: "\f005"; 211 | } 212 | .fa-star-o:before { 213 | content: "\f006"; 214 | } 215 | .fa-user:before { 216 | content: "\f007"; 217 | } 218 | .fa-film:before { 219 | content: "\f008"; 220 | } 221 | .fa-th-large:before { 222 | content: "\f009"; 223 | } 224 | .fa-th:before { 225 | content: "\f00a"; 226 | } 227 | .fa-th-list:before { 228 | content: "\f00b"; 229 | } 230 | .fa-check:before { 231 | content: "\f00c"; 232 | } 233 | .fa-times:before { 234 | content: "\f00d"; 235 | } 236 | .fa-search-plus:before { 237 | content: "\f00e"; 238 | } 239 | .fa-search-minus:before { 240 | content: "\f010"; 241 | } 242 | .fa-power-off:before { 243 | content: "\f011"; 244 | } 245 | .fa-signal:before { 246 | content: "\f012"; 247 | } 248 | .fa-gear:before, 249 | .fa-cog:before { 250 | content: "\f013"; 251 | } 252 | .fa-trash-o:before { 253 | content: "\f014"; 254 | } 255 | .fa-home:before { 256 | content: "\f015"; 257 | } 258 | .fa-file-o:before { 259 | content: "\f016"; 260 | } 261 | .fa-clock-o:before { 262 | content: "\f017"; 263 | } 264 | .fa-road:before { 265 | content: "\f018"; 266 | } 267 | .fa-download:before { 268 | content: "\f019"; 269 | } 270 | .fa-arrow-circle-o-down:before { 271 | content: "\f01a"; 272 | } 273 | .fa-arrow-circle-o-up:before { 274 | content: "\f01b"; 275 | } 276 | .fa-inbox:before { 277 | content: "\f01c"; 278 | } 279 | .fa-play-circle-o:before { 280 | content: "\f01d"; 281 | } 282 | .fa-rotate-right:before, 283 | .fa-repeat:before { 284 | content: "\f01e"; 285 | } 286 | .fa-refresh:before { 287 | content: "\f021"; 288 | } 289 | .fa-list-alt:before { 290 | content: "\f022"; 291 | } 292 | .fa-lock:before { 293 | content: "\f023"; 294 | } 295 | .fa-flag:before { 296 | content: "\f024"; 297 | } 298 | .fa-headphones:before { 299 | content: "\f025"; 300 | } 301 | .fa-volume-off:before { 302 | content: "\f026"; 303 | } 304 | .fa-volume-down:before { 305 | content: "\f027"; 306 | } 307 | .fa-volume-up:before { 308 | content: "\f028"; 309 | } 310 | .fa-qrcode:before { 311 | content: "\f029"; 312 | } 313 | .fa-barcode:before { 314 | content: "\f02a"; 315 | } 316 | .fa-tag:before { 317 | content: "\f02b"; 318 | } 319 | .fa-tags:before { 320 | content: "\f02c"; 321 | } 322 | .fa-book:before { 323 | content: "\f02d"; 324 | } 325 | .fa-bookmark:before { 326 | content: "\f02e"; 327 | } 328 | .fa-print:before { 329 | content: "\f02f"; 330 | } 331 | .fa-camera:before { 332 | content: "\f030"; 333 | } 334 | .fa-font:before { 335 | content: "\f031"; 336 | } 337 | .fa-bold:before { 338 | content: "\f032"; 339 | } 340 | .fa-italic:before { 341 | content: "\f033"; 342 | } 343 | .fa-text-height:before { 344 | content: "\f034"; 345 | } 346 | .fa-text-width:before { 347 | content: "\f035"; 348 | } 349 | .fa-align-left:before { 350 | content: "\f036"; 351 | } 352 | .fa-align-center:before { 353 | content: "\f037"; 354 | } 355 | .fa-align-right:before { 356 | content: "\f038"; 357 | } 358 | .fa-align-justify:before { 359 | content: "\f039"; 360 | } 361 | .fa-list:before { 362 | content: "\f03a"; 363 | } 364 | .fa-dedent:before, 365 | .fa-outdent:before { 366 | content: "\f03b"; 367 | } 368 | .fa-indent:before { 369 | content: "\f03c"; 370 | } 371 | .fa-video-camera:before { 372 | content: "\f03d"; 373 | } 374 | .fa-picture-o:before { 375 | content: "\f03e"; 376 | } 377 | .fa-pencil:before { 378 | content: "\f040"; 379 | } 380 | .fa-map-marker:before { 381 | content: "\f041"; 382 | } 383 | .fa-adjust:before { 384 | content: "\f042"; 385 | } 386 | .fa-tint:before { 387 | content: "\f043"; 388 | } 389 | .fa-edit:before, 390 | .fa-pencil-square-o:before { 391 | content: "\f044"; 392 | } 393 | .fa-share-square-o:before { 394 | content: "\f045"; 395 | } 396 | .fa-check-square-o:before { 397 | content: "\f046"; 398 | } 399 | .fa-arrows:before { 400 | content: "\f047"; 401 | } 402 | .fa-step-backward:before { 403 | content: "\f048"; 404 | } 405 | .fa-fast-backward:before { 406 | content: "\f049"; 407 | } 408 | .fa-backward:before { 409 | content: "\f04a"; 410 | } 411 | .fa-play:before { 412 | content: "\f04b"; 413 | } 414 | .fa-pause:before { 415 | content: "\f04c"; 416 | } 417 | .fa-stop:before { 418 | content: "\f04d"; 419 | } 420 | .fa-forward:before { 421 | content: "\f04e"; 422 | } 423 | .fa-fast-forward:before { 424 | content: "\f050"; 425 | } 426 | .fa-step-forward:before { 427 | content: "\f051"; 428 | } 429 | .fa-eject:before { 430 | content: "\f052"; 431 | } 432 | .fa-chevron-left:before { 433 | content: "\f053"; 434 | } 435 | .fa-chevron-right:before { 436 | content: "\f054"; 437 | } 438 | .fa-plus-circle:before { 439 | content: "\f055"; 440 | } 441 | .fa-minus-circle:before { 442 | content: "\f056"; 443 | } 444 | .fa-times-circle:before { 445 | content: "\f057"; 446 | } 447 | .fa-check-circle:before { 448 | content: "\f058"; 449 | } 450 | .fa-question-circle:before { 451 | content: "\f059"; 452 | } 453 | .fa-info-circle:before { 454 | content: "\f05a"; 455 | } 456 | .fa-crosshairs:before { 457 | content: "\f05b"; 458 | } 459 | .fa-times-circle-o:before { 460 | content: "\f05c"; 461 | } 462 | .fa-check-circle-o:before { 463 | content: "\f05d"; 464 | } 465 | .fa-ban:before { 466 | content: "\f05e"; 467 | } 468 | .fa-arrow-left:before { 469 | content: "\f060"; 470 | } 471 | .fa-arrow-right:before { 472 | content: "\f061"; 473 | } 474 | .fa-arrow-up:before { 475 | content: "\f062"; 476 | } 477 | .fa-arrow-down:before { 478 | content: "\f063"; 479 | } 480 | .fa-mail-forward:before, 481 | .fa-share:before { 482 | content: "\f064"; 483 | } 484 | .fa-expand:before { 485 | content: "\f065"; 486 | } 487 | .fa-compress:before { 488 | content: "\f066"; 489 | } 490 | .fa-plus:before { 491 | content: "\f067"; 492 | } 493 | .fa-minus:before { 494 | content: "\f068"; 495 | } 496 | .fa-asterisk:before { 497 | content: "\f069"; 498 | } 499 | .fa-exclamation-circle:before { 500 | content: "\f06a"; 501 | } 502 | .fa-gift:before { 503 | content: "\f06b"; 504 | } 505 | .fa-leaf:before { 506 | content: "\f06c"; 507 | } 508 | .fa-fire:before { 509 | content: "\f06d"; 510 | } 511 | .fa-eye:before { 512 | content: "\f06e"; 513 | } 514 | .fa-eye-slash:before { 515 | content: "\f070"; 516 | } 517 | .fa-warning:before, 518 | .fa-exclamation-triangle:before { 519 | content: "\f071"; 520 | } 521 | .fa-plane:before { 522 | content: "\f072"; 523 | } 524 | .fa-calendar:before { 525 | content: "\f073"; 526 | } 527 | .fa-random:before { 528 | content: "\f074"; 529 | } 530 | .fa-comment:before { 531 | content: "\f075"; 532 | } 533 | .fa-magnet:before { 534 | content: "\f076"; 535 | } 536 | .fa-chevron-up:before { 537 | content: "\f077"; 538 | } 539 | .fa-chevron-down:before { 540 | content: "\f078"; 541 | } 542 | .fa-retweet:before { 543 | content: "\f079"; 544 | } 545 | .fa-shopping-cart:before { 546 | content: "\f07a"; 547 | } 548 | .fa-folder:before { 549 | content: "\f07b"; 550 | } 551 | .fa-folder-open:before { 552 | content: "\f07c"; 553 | } 554 | .fa-arrows-v:before { 555 | content: "\f07d"; 556 | } 557 | .fa-arrows-h:before { 558 | content: "\f07e"; 559 | } 560 | .fa-bar-chart-o:before { 561 | content: "\f080"; 562 | } 563 | .fa-twitter-square:before { 564 | content: "\f081"; 565 | } 566 | .fa-facebook-square:before { 567 | content: "\f082"; 568 | } 569 | .fa-camera-retro:before { 570 | content: "\f083"; 571 | } 572 | .fa-key:before { 573 | content: "\f084"; 574 | } 575 | .fa-gears:before, 576 | .fa-cogs:before { 577 | content: "\f085"; 578 | } 579 | .fa-comments:before { 580 | content: "\f086"; 581 | } 582 | .fa-thumbs-o-up:before { 583 | content: "\f087"; 584 | } 585 | .fa-thumbs-o-down:before { 586 | content: "\f088"; 587 | } 588 | .fa-star-half:before { 589 | content: "\f089"; 590 | } 591 | .fa-heart-o:before { 592 | content: "\f08a"; 593 | } 594 | .fa-sign-out:before { 595 | content: "\f08b"; 596 | } 597 | .fa-linkedin-square:before { 598 | content: "\f08c"; 599 | } 600 | .fa-thumb-tack:before { 601 | content: "\f08d"; 602 | } 603 | .fa-external-link:before { 604 | content: "\f08e"; 605 | } 606 | .fa-sign-in:before { 607 | content: "\f090"; 608 | } 609 | .fa-trophy:before { 610 | content: "\f091"; 611 | } 612 | .fa-github-square:before { 613 | content: "\f092"; 614 | } 615 | .fa-upload:before { 616 | content: "\f093"; 617 | } 618 | .fa-lemon-o:before { 619 | content: "\f094"; 620 | } 621 | .fa-phone:before { 622 | content: "\f095"; 623 | } 624 | .fa-square-o:before { 625 | content: "\f096"; 626 | } 627 | .fa-bookmark-o:before { 628 | content: "\f097"; 629 | } 630 | .fa-phone-square:before { 631 | content: "\f098"; 632 | } 633 | .fa-twitter:before { 634 | content: "\f099"; 635 | } 636 | .fa-facebook:before { 637 | content: "\f09a"; 638 | } 639 | .fa-github:before { 640 | content: "\f09b"; 641 | } 642 | .fa-unlock:before { 643 | content: "\f09c"; 644 | } 645 | .fa-credit-card:before { 646 | content: "\f09d"; 647 | } 648 | .fa-rss:before { 649 | content: "\f09e"; 650 | } 651 | .fa-hdd-o:before { 652 | content: "\f0a0"; 653 | } 654 | .fa-bullhorn:before { 655 | content: "\f0a1"; 656 | } 657 | .fa-bell:before { 658 | content: "\f0f3"; 659 | } 660 | .fa-certificate:before { 661 | content: "\f0a3"; 662 | } 663 | .fa-hand-o-right:before { 664 | content: "\f0a4"; 665 | } 666 | .fa-hand-o-left:before { 667 | content: "\f0a5"; 668 | } 669 | .fa-hand-o-up:before { 670 | content: "\f0a6"; 671 | } 672 | .fa-hand-o-down:before { 673 | content: "\f0a7"; 674 | } 675 | .fa-arrow-circle-left:before { 676 | content: "\f0a8"; 677 | } 678 | .fa-arrow-circle-right:before { 679 | content: "\f0a9"; 680 | } 681 | .fa-arrow-circle-up:before { 682 | content: "\f0aa"; 683 | } 684 | .fa-arrow-circle-down:before { 685 | content: "\f0ab"; 686 | } 687 | .fa-globe:before { 688 | content: "\f0ac"; 689 | } 690 | .fa-wrench:before { 691 | content: "\f0ad"; 692 | } 693 | .fa-tasks:before { 694 | content: "\f0ae"; 695 | } 696 | .fa-filter:before { 697 | content: "\f0b0"; 698 | } 699 | .fa-briefcase:before { 700 | content: "\f0b1"; 701 | } 702 | .fa-arrows-alt:before { 703 | content: "\f0b2"; 704 | } 705 | .fa-group:before, 706 | .fa-users:before { 707 | content: "\f0c0"; 708 | } 709 | .fa-chain:before, 710 | .fa-link:before { 711 | content: "\f0c1"; 712 | } 713 | .fa-cloud:before { 714 | content: "\f0c2"; 715 | } 716 | .fa-flask:before { 717 | content: "\f0c3"; 718 | } 719 | .fa-cut:before, 720 | .fa-scissors:before { 721 | content: "\f0c4"; 722 | } 723 | .fa-copy:before, 724 | .fa-files-o:before { 725 | content: "\f0c5"; 726 | } 727 | .fa-paperclip:before { 728 | content: "\f0c6"; 729 | } 730 | .fa-save:before, 731 | .fa-floppy-o:before { 732 | content: "\f0c7"; 733 | } 734 | .fa-square:before { 735 | content: "\f0c8"; 736 | } 737 | .fa-bars:before { 738 | content: "\f0c9"; 739 | } 740 | .fa-list-ul:before { 741 | content: "\f0ca"; 742 | } 743 | .fa-list-ol:before { 744 | content: "\f0cb"; 745 | } 746 | .fa-strikethrough:before { 747 | content: "\f0cc"; 748 | } 749 | .fa-underline:before { 750 | content: "\f0cd"; 751 | } 752 | .fa-table:before { 753 | content: "\f0ce"; 754 | } 755 | .fa-magic:before { 756 | content: "\f0d0"; 757 | } 758 | .fa-truck:before { 759 | content: "\f0d1"; 760 | } 761 | .fa-pinterest:before { 762 | content: "\f0d2"; 763 | } 764 | .fa-pinterest-square:before { 765 | content: "\f0d3"; 766 | } 767 | .fa-google-plus-square:before { 768 | content: "\f0d4"; 769 | } 770 | .fa-google-plus:before { 771 | content: "\f0d5"; 772 | } 773 | .fa-money:before { 774 | content: "\f0d6"; 775 | } 776 | .fa-caret-down:before { 777 | content: "\f0d7"; 778 | } 779 | .fa-caret-up:before { 780 | content: "\f0d8"; 781 | } 782 | .fa-caret-left:before { 783 | content: "\f0d9"; 784 | } 785 | .fa-caret-right:before { 786 | content: "\f0da"; 787 | } 788 | .fa-columns:before { 789 | content: "\f0db"; 790 | } 791 | .fa-unsorted:before, 792 | .fa-sort:before { 793 | content: "\f0dc"; 794 | } 795 | .fa-sort-down:before, 796 | .fa-sort-asc:before { 797 | content: "\f0dd"; 798 | } 799 | .fa-sort-up:before, 800 | .fa-sort-desc:before { 801 | content: "\f0de"; 802 | } 803 | .fa-envelope:before { 804 | content: "\f0e0"; 805 | } 806 | .fa-linkedin:before { 807 | content: "\f0e1"; 808 | } 809 | .fa-rotate-left:before, 810 | .fa-undo:before { 811 | content: "\f0e2"; 812 | } 813 | .fa-legal:before, 814 | .fa-gavel:before { 815 | content: "\f0e3"; 816 | } 817 | .fa-dashboard:before, 818 | .fa-tachometer:before { 819 | content: "\f0e4"; 820 | } 821 | .fa-comment-o:before { 822 | content: "\f0e5"; 823 | } 824 | .fa-comments-o:before { 825 | content: "\f0e6"; 826 | } 827 | .fa-flash:before, 828 | .fa-bolt:before { 829 | content: "\f0e7"; 830 | } 831 | .fa-sitemap:before { 832 | content: "\f0e8"; 833 | } 834 | .fa-umbrella:before { 835 | content: "\f0e9"; 836 | } 837 | .fa-paste:before, 838 | .fa-clipboard:before { 839 | content: "\f0ea"; 840 | } 841 | .fa-lightbulb-o:before { 842 | content: "\f0eb"; 843 | } 844 | .fa-exchange:before { 845 | content: "\f0ec"; 846 | } 847 | .fa-cloud-download:before { 848 | content: "\f0ed"; 849 | } 850 | .fa-cloud-upload:before { 851 | content: "\f0ee"; 852 | } 853 | .fa-user-md:before { 854 | content: "\f0f0"; 855 | } 856 | .fa-stethoscope:before { 857 | content: "\f0f1"; 858 | } 859 | .fa-suitcase:before { 860 | content: "\f0f2"; 861 | } 862 | .fa-bell-o:before { 863 | content: "\f0a2"; 864 | } 865 | .fa-coffee:before { 866 | content: "\f0f4"; 867 | } 868 | .fa-cutlery:before { 869 | content: "\f0f5"; 870 | } 871 | .fa-file-text-o:before { 872 | content: "\f0f6"; 873 | } 874 | .fa-building-o:before { 875 | content: "\f0f7"; 876 | } 877 | .fa-hospital-o:before { 878 | content: "\f0f8"; 879 | } 880 | .fa-ambulance:before { 881 | content: "\f0f9"; 882 | } 883 | .fa-medkit:before { 884 | content: "\f0fa"; 885 | } 886 | .fa-fighter-jet:before { 887 | content: "\f0fb"; 888 | } 889 | .fa-beer:before { 890 | content: "\f0fc"; 891 | } 892 | .fa-h-square:before { 893 | content: "\f0fd"; 894 | } 895 | .fa-plus-square:before { 896 | content: "\f0fe"; 897 | } 898 | .fa-angle-double-left:before { 899 | content: "\f100"; 900 | } 901 | .fa-angle-double-right:before { 902 | content: "\f101"; 903 | } 904 | .fa-angle-double-up:before { 905 | content: "\f102"; 906 | } 907 | .fa-angle-double-down:before { 908 | content: "\f103"; 909 | } 910 | .fa-angle-left:before { 911 | content: "\f104"; 912 | } 913 | .fa-angle-right:before { 914 | content: "\f105"; 915 | } 916 | .fa-angle-up:before { 917 | content: "\f106"; 918 | } 919 | .fa-angle-down:before { 920 | content: "\f107"; 921 | } 922 | .fa-desktop:before { 923 | content: "\f108"; 924 | } 925 | .fa-laptop:before { 926 | content: "\f109"; 927 | } 928 | .fa-tablet:before { 929 | content: "\f10a"; 930 | } 931 | .fa-mobile-phone:before, 932 | .fa-mobile:before { 933 | content: "\f10b"; 934 | } 935 | .fa-circle-o:before { 936 | content: "\f10c"; 937 | } 938 | .fa-quote-left:before { 939 | content: "\f10d"; 940 | } 941 | .fa-quote-right:before { 942 | content: "\f10e"; 943 | } 944 | .fa-spinner:before { 945 | content: "\f110"; 946 | } 947 | .fa-circle:before { 948 | content: "\f111"; 949 | } 950 | .fa-mail-reply:before, 951 | .fa-reply:before { 952 | content: "\f112"; 953 | } 954 | .fa-github-alt:before { 955 | content: "\f113"; 956 | } 957 | .fa-folder-o:before { 958 | content: "\f114"; 959 | } 960 | .fa-folder-open-o:before { 961 | content: "\f115"; 962 | } 963 | .fa-smile-o:before { 964 | content: "\f118"; 965 | } 966 | .fa-frown-o:before { 967 | content: "\f119"; 968 | } 969 | .fa-meh-o:before { 970 | content: "\f11a"; 971 | } 972 | .fa-gamepad:before { 973 | content: "\f11b"; 974 | } 975 | .fa-keyboard-o:before { 976 | content: "\f11c"; 977 | } 978 | .fa-flag-o:before { 979 | content: "\f11d"; 980 | } 981 | .fa-flag-checkered:before { 982 | content: "\f11e"; 983 | } 984 | .fa-terminal:before { 985 | content: "\f120"; 986 | } 987 | .fa-code:before { 988 | content: "\f121"; 989 | } 990 | .fa-reply-all:before { 991 | content: "\f122"; 992 | } 993 | .fa-mail-reply-all:before { 994 | content: "\f122"; 995 | } 996 | .fa-star-half-empty:before, 997 | .fa-star-half-full:before, 998 | .fa-star-half-o:before { 999 | content: "\f123"; 1000 | } 1001 | .fa-location-arrow:before { 1002 | content: "\f124"; 1003 | } 1004 | .fa-crop:before { 1005 | content: "\f125"; 1006 | } 1007 | .fa-code-fork:before { 1008 | content: "\f126"; 1009 | } 1010 | .fa-unlink:before, 1011 | .fa-chain-broken:before { 1012 | content: "\f127"; 1013 | } 1014 | .fa-question:before { 1015 | content: "\f128"; 1016 | } 1017 | .fa-info:before { 1018 | content: "\f129"; 1019 | } 1020 | .fa-exclamation:before { 1021 | content: "\f12a"; 1022 | } 1023 | .fa-superscript:before { 1024 | content: "\f12b"; 1025 | } 1026 | .fa-subscript:before { 1027 | content: "\f12c"; 1028 | } 1029 | .fa-eraser:before { 1030 | content: "\f12d"; 1031 | } 1032 | .fa-puzzle-piece:before { 1033 | content: "\f12e"; 1034 | } 1035 | .fa-microphone:before { 1036 | content: "\f130"; 1037 | } 1038 | .fa-microphone-slash:before { 1039 | content: "\f131"; 1040 | } 1041 | .fa-shield:before { 1042 | content: "\f132"; 1043 | } 1044 | .fa-calendar-o:before { 1045 | content: "\f133"; 1046 | } 1047 | .fa-fire-extinguisher:before { 1048 | content: "\f134"; 1049 | } 1050 | .fa-rocket:before { 1051 | content: "\f135"; 1052 | } 1053 | .fa-maxcdn:before { 1054 | content: "\f136"; 1055 | } 1056 | .fa-chevron-circle-left:before { 1057 | content: "\f137"; 1058 | } 1059 | .fa-chevron-circle-right:before { 1060 | content: "\f138"; 1061 | } 1062 | .fa-chevron-circle-up:before { 1063 | content: "\f139"; 1064 | } 1065 | .fa-chevron-circle-down:before { 1066 | content: "\f13a"; 1067 | } 1068 | .fa-html5:before { 1069 | content: "\f13b"; 1070 | } 1071 | .fa-css3:before { 1072 | content: "\f13c"; 1073 | } 1074 | .fa-anchor:before { 1075 | content: "\f13d"; 1076 | } 1077 | .fa-unlock-alt:before { 1078 | content: "\f13e"; 1079 | } 1080 | .fa-bullseye:before { 1081 | content: "\f140"; 1082 | } 1083 | .fa-ellipsis-h:before { 1084 | content: "\f141"; 1085 | } 1086 | .fa-ellipsis-v:before { 1087 | content: "\f142"; 1088 | } 1089 | .fa-rss-square:before { 1090 | content: "\f143"; 1091 | } 1092 | .fa-play-circle:before { 1093 | content: "\f144"; 1094 | } 1095 | .fa-ticket:before { 1096 | content: "\f145"; 1097 | } 1098 | .fa-minus-square:before { 1099 | content: "\f146"; 1100 | } 1101 | .fa-minus-square-o:before { 1102 | content: "\f147"; 1103 | } 1104 | .fa-level-up:before { 1105 | content: "\f148"; 1106 | } 1107 | .fa-level-down:before { 1108 | content: "\f149"; 1109 | } 1110 | .fa-check-square:before { 1111 | content: "\f14a"; 1112 | } 1113 | .fa-pencil-square:before { 1114 | content: "\f14b"; 1115 | } 1116 | .fa-external-link-square:before { 1117 | content: "\f14c"; 1118 | } 1119 | .fa-share-square:before { 1120 | content: "\f14d"; 1121 | } 1122 | .fa-compass:before { 1123 | content: "\f14e"; 1124 | } 1125 | .fa-toggle-down:before, 1126 | .fa-caret-square-o-down:before { 1127 | content: "\f150"; 1128 | } 1129 | .fa-toggle-up:before, 1130 | .fa-caret-square-o-up:before { 1131 | content: "\f151"; 1132 | } 1133 | .fa-toggle-right:before, 1134 | .fa-caret-square-o-right:before { 1135 | content: "\f152"; 1136 | } 1137 | .fa-euro:before, 1138 | .fa-eur:before { 1139 | content: "\f153"; 1140 | } 1141 | .fa-gbp:before { 1142 | content: "\f154"; 1143 | } 1144 | .fa-dollar:before, 1145 | .fa-usd:before { 1146 | content: "\f155"; 1147 | } 1148 | .fa-rupee:before, 1149 | .fa-inr:before { 1150 | content: "\f156"; 1151 | } 1152 | .fa-cny:before, 1153 | .fa-rmb:before, 1154 | .fa-yen:before, 1155 | .fa-jpy:before { 1156 | content: "\f157"; 1157 | } 1158 | .fa-ruble:before, 1159 | .fa-rouble:before, 1160 | .fa-rub:before { 1161 | content: "\f158"; 1162 | } 1163 | .fa-won:before, 1164 | .fa-krw:before { 1165 | content: "\f159"; 1166 | } 1167 | .fa-bitcoin:before, 1168 | .fa-btc:before { 1169 | content: "\f15a"; 1170 | } 1171 | .fa-file:before { 1172 | content: "\f15b"; 1173 | } 1174 | .fa-file-text:before { 1175 | content: "\f15c"; 1176 | } 1177 | .fa-sort-alpha-asc:before { 1178 | content: "\f15d"; 1179 | } 1180 | .fa-sort-alpha-desc:before { 1181 | content: "\f15e"; 1182 | } 1183 | .fa-sort-amount-asc:before { 1184 | content: "\f160"; 1185 | } 1186 | .fa-sort-amount-desc:before { 1187 | content: "\f161"; 1188 | } 1189 | .fa-sort-numeric-asc:before { 1190 | content: "\f162"; 1191 | } 1192 | .fa-sort-numeric-desc:before { 1193 | content: "\f163"; 1194 | } 1195 | .fa-thumbs-up:before { 1196 | content: "\f164"; 1197 | } 1198 | .fa-thumbs-down:before { 1199 | content: "\f165"; 1200 | } 1201 | .fa-youtube-square:before { 1202 | content: "\f166"; 1203 | } 1204 | .fa-youtube:before { 1205 | content: "\f167"; 1206 | } 1207 | .fa-xing:before { 1208 | content: "\f168"; 1209 | } 1210 | .fa-xing-square:before { 1211 | content: "\f169"; 1212 | } 1213 | .fa-youtube-play:before { 1214 | content: "\f16a"; 1215 | } 1216 | .fa-dropbox:before { 1217 | content: "\f16b"; 1218 | } 1219 | .fa-stack-overflow:before { 1220 | content: "\f16c"; 1221 | } 1222 | .fa-instagram:before { 1223 | content: "\f16d"; 1224 | } 1225 | .fa-flickr:before { 1226 | content: "\f16e"; 1227 | } 1228 | .fa-adn:before { 1229 | content: "\f170"; 1230 | } 1231 | .fa-bitbucket:before { 1232 | content: "\f171"; 1233 | } 1234 | .fa-bitbucket-square:before { 1235 | content: "\f172"; 1236 | } 1237 | .fa-tumblr:before { 1238 | content: "\f173"; 1239 | } 1240 | .fa-tumblr-square:before { 1241 | content: "\f174"; 1242 | } 1243 | .fa-long-arrow-down:before { 1244 | content: "\f175"; 1245 | } 1246 | .fa-long-arrow-up:before { 1247 | content: "\f176"; 1248 | } 1249 | .fa-long-arrow-left:before { 1250 | content: "\f177"; 1251 | } 1252 | .fa-long-arrow-right:before { 1253 | content: "\f178"; 1254 | } 1255 | .fa-apple:before { 1256 | content: "\f179"; 1257 | } 1258 | .fa-windows:before { 1259 | content: "\f17a"; 1260 | } 1261 | .fa-android:before { 1262 | content: "\f17b"; 1263 | } 1264 | .fa-linux:before { 1265 | content: "\f17c"; 1266 | } 1267 | .fa-dribbble:before { 1268 | content: "\f17d"; 1269 | } 1270 | .fa-skype:before { 1271 | content: "\f17e"; 1272 | } 1273 | .fa-foursquare:before { 1274 | content: "\f180"; 1275 | } 1276 | .fa-trello:before { 1277 | content: "\f181"; 1278 | } 1279 | .fa-female:before { 1280 | content: "\f182"; 1281 | } 1282 | .fa-male:before { 1283 | content: "\f183"; 1284 | } 1285 | .fa-gittip:before { 1286 | content: "\f184"; 1287 | } 1288 | .fa-sun-o:before { 1289 | content: "\f185"; 1290 | } 1291 | .fa-moon-o:before { 1292 | content: "\f186"; 1293 | } 1294 | .fa-archive:before { 1295 | content: "\f187"; 1296 | } 1297 | .fa-bug:before { 1298 | content: "\f188"; 1299 | } 1300 | .fa-vk:before { 1301 | content: "\f189"; 1302 | } 1303 | .fa-weibo:before { 1304 | content: "\f18a"; 1305 | } 1306 | .fa-renren:before { 1307 | content: "\f18b"; 1308 | } 1309 | .fa-pagelines:before { 1310 | content: "\f18c"; 1311 | } 1312 | .fa-stack-exchange:before { 1313 | content: "\f18d"; 1314 | } 1315 | .fa-arrow-circle-o-right:before { 1316 | content: "\f18e"; 1317 | } 1318 | .fa-arrow-circle-o-left:before { 1319 | content: "\f190"; 1320 | } 1321 | .fa-toggle-left:before, 1322 | .fa-caret-square-o-left:before { 1323 | content: "\f191"; 1324 | } 1325 | .fa-dot-circle-o:before { 1326 | content: "\f192"; 1327 | } 1328 | .fa-wheelchair:before { 1329 | content: "\f193"; 1330 | } 1331 | .fa-vimeo-square:before { 1332 | content: "\f194"; 1333 | } 1334 | .fa-turkish-lira:before, 1335 | .fa-try:before { 1336 | content: "\f195"; 1337 | } 1338 | .fa-plus-square-o:before { 1339 | content: "\f196"; 1340 | } 1341 | html._TEST_END_FILE2 {font-size: 1em;} --------------------------------------------------------------------------------