├── .gitignore ├── images ├── Main menu.png ├── Searching example.png └── Highlighting example.png ├── src ├── content_scripts │ ├── main.js │ ├── remove_highlights.js │ └── page_search.js ├── options │ ├── options.css │ ├── options.html │ └── options.js ├── icons │ ├── RegexSearch-32.png │ ├── RegexSearch-48.png │ ├── RegexSearch-popup.png │ ├── RegexSearch.svg │ └── RegexSearch popup.svg ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── css │ ├── style.css │ └── bootstrap-theme.min.css ├── js │ ├── npm.js │ └── bootstrap.js ├── popup │ ├── RegexSearch.css │ ├── RegexSearch.html │ └── RegexSearch.js └── manifest.json ├── package.json ├── vite.contentscript.config.js ├── README.md ├── LICENSE ├── vite.config.js └── help.md /.gitignore: -------------------------------------------------------------------------------- 1 | /releases 2 | /web-ext-artifacts 3 | /build 4 | /node_modules 5 | 6 | -------------------------------------------------------------------------------- /images/Main menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/images/Main menu.png -------------------------------------------------------------------------------- /src/content_scripts/main.js: -------------------------------------------------------------------------------- 1 | import './page_search.js'; 2 | import './remove_highlights.js'; 3 | -------------------------------------------------------------------------------- /src/options/options.css: -------------------------------------------------------------------------------- 1 | html , body { 2 | background-color: inherit; 3 | font-size: 90%; 4 | } 5 | -------------------------------------------------------------------------------- /images/Searching example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/images/Searching example.png -------------------------------------------------------------------------------- /src/icons/RegexSearch-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/icons/RegexSearch-32.png -------------------------------------------------------------------------------- /src/icons/RegexSearch-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/icons/RegexSearch-48.png -------------------------------------------------------------------------------- /images/Highlighting example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/images/Highlighting example.png -------------------------------------------------------------------------------- /src/icons/RegexSearch-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/icons/RegexSearch-popup.png -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mohd-PH/RegexSearch/HEAD/src/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto+Mono'); 2 | .monospaced { 3 | font-family: 'Roboto Mono', monospace; 4 | } -------------------------------------------------------------------------------- /src/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Saved Profiles

9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RegexSearch", 3 | "version": "0.0.1", 4 | "description": "[description]", 5 | "scripts": { 6 | "dev": "", 7 | "build": "vite build && vite build --config ./vite.contentscript.config.js", 8 | "start:firefox": "web-ext run --source-dir ./build --target=firefox-desktop --devtools" 9 | }, 10 | "dependencies": { 11 | "@rollup/plugin-inject": "^5.0.3", 12 | "bootstrap": "^3.3.7", 13 | "jquery": "^3.2.1", 14 | "npm": "^9.6.6", 15 | "vite": "^4.3.6", 16 | "vite-plugin-static-copy": "^0.15.0", 17 | "web-ext": "^7.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vite.contentscript.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, normalizePath } from "vite"; 2 | 3 | export default defineConfig({ 4 | build: { 5 | emptyOutDir: false, 6 | outDir: "build", 7 | rollupOptions: { 8 | // https://rollupjs.org/configuration-options/ 9 | input: "./src/content_scripts/main.js", 10 | output:{ 11 | inlineDynamicImports: true, 12 | format: "iife", 13 | extend: true, 14 | entryFileNames: "content_script.js", 15 | dir: "build", 16 | } 17 | }, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /src/popup/RegexSearch.css: -------------------------------------------------------------------------------- 1 | html , body { 2 | min-width : 500px ; 3 | } 4 | 5 | .content { 6 | padding: 20px; 7 | } 8 | 9 | #standardColors { 10 | margin: 0 13px 5px; 11 | } 12 | 13 | .label { 14 | padding: 3px; 15 | border-radius: 5px; 16 | color: black; 17 | } 18 | 19 | #profilesList{ 20 | left: -55px; 21 | } 22 | 23 | #yellowLabel { 24 | background-color: yellow; 25 | } 26 | 27 | #blueLabel { 28 | background-color: cyan; 29 | } 30 | 31 | #greenLabel { 32 | background-color: lawngreen; 33 | } 34 | 35 | #pinkLabel { 36 | background-color: hotpink; 37 | } 38 | 39 | #purpleLabel { 40 | background-color: magenta; 41 | } 42 | 43 | .small-form{ 44 | display:none; 45 | } 46 | 47 | #small-form{ 48 | padding: 3px 20px; 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RegexSearch 2 | Firefox Extension (WebExtension) to Search in HTML using Regular Expression (REGEX) and output the results using template, with saving profiles functionality, you need to know 'basic' Regular expression to benefit from this plugin 3 | 4 | # Screenshots 5 | ![Main menu](https://raw.githubusercontent.com/Mohd-PH/RegexSearch/master/images/Main%20menu.png) Main menu| 6 | ![Highlighting example](https://raw.githubusercontent.com/Mohd-PH/RegexSearch/master/images/Highlighting%20example.png) Highlighting example| 7 | ![Searching example](https://raw.githubusercontent.com/Mohd-PH/RegexSearch/master/images/Searching%20example.png) Searching example 8 | --------|-------|------- 9 | 10 | 11 | # How to use 12 | [Here is help file written](help.md) by [@theDragonFire](https://github.com/theDragonFire) 13 | -------------------------------------------------------------------------------- /src/content_scripts/remove_highlights.js: -------------------------------------------------------------------------------- 1 | function remove_highlighting(request) { 2 | if(request.action == "remove-highlights") { 3 | var previousHighlighters = document.querySelectorAll(".regexSearchHighlighter"); 4 | 5 | previousHighlighters.forEach( (highlight) => { 6 | highlight.style.backgroundColor = "transparent"; 7 | highlight.style.border = ""; 8 | highlight.classList.remove("regexSearchHighlighter"); 9 | }); 10 | previousHighlighters = document.querySelectorAll(".regexSearchMatch"); 11 | previousHighlighters.forEach( (highlight) => { 12 | highlight.style.backgroundColor = "transparent"; 13 | highlight.style.border = ""; 14 | highlight.classList.remove("regexSearchMatch"); 15 | }); 16 | } 17 | 18 | browser.runtime.onMessage.removeListener(remove_highlighting); 19 | } 20 | 21 | browser.runtime.onMessage.addListener(remove_highlighting); 22 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Regex Search", 4 | "version": "1.0.17", 5 | "description": "Search in HTML using Regular Expression (REGEX) and output the results using template, with saving profiles functionality, you need to know 'basic' Regular expression to benefit from this plugin", 6 | "homepage_url": "https://github.com/Mohd-PH/RegexSearch/", 7 | "icons": { 8 | "48": "icons/RegexSearch.svg", 9 | "96": "icons/RegexSearch.svg" 10 | }, 11 | "permissions": [ 12 | "activeTab", 13 | "storage", 14 | "clipboardWrite" 15 | ], 16 | "browser_action": { 17 | "default_icon": "icons/RegexSearch-32.png", 18 | "default_title": "Regex Search", 19 | "default_popup": "popup/RegexSearch.html" 20 | }, 21 | "options_ui": { 22 | "page": "options/options.html" 23 | }, 24 | "commands":{ 25 | "_execute_browser_action":{ 26 | "suggested_key": { 27 | "default": "Ctrl+Shift+F" 28 | }, 29 | "description": "Opens search popup" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mohd-PH 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, normalizePath } from "vite"; 2 | import { viteStaticCopy } from 'vite-plugin-static-copy'; 3 | import path from "node:path"; 4 | import inject from "@rollup/plugin-inject"; 5 | 6 | export default defineConfig({ 7 | root: "./src", 8 | plugins: [ 9 | viteStaticCopy({ 10 | targets: [ 11 | { 12 | src: normalizePath(path.resolve(__dirname, './src/manifest.json')), 13 | dest: normalizePath(path.resolve(__dirname, './build')), 14 | }, 15 | { 16 | src: normalizePath(path.resolve(__dirname, './src/icons')), 17 | dest: normalizePath(path.resolve(__dirname, './build')), 18 | }, 19 | ] 20 | }), 21 | inject({ 22 | $: 'jquery', 23 | jQuery: 'jquery', 24 | }), 25 | ], 26 | build: { 27 | outDir: "build", 28 | rollupOptions: { 29 | // https://rollupjs.org/configuration-options/ 30 | input: [ 31 | "./src/popup/RegexSearch.html", 32 | "./src/options/options.html", 33 | ], 34 | output:{ 35 | format: "es", 36 | dir: "build", 37 | entryFileNames: "[name].js", 38 | } 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /help.md: -------------------------------------------------------------------------------- 1 | ##### Special Regex Characters 2 | * `.` - matches any character 3 | * `\d` - matches digits 0-9 4 | * `\D` - matches any character but 0-9 5 | * `\s` - matches whitespace characters 6 | * `\S` - matches any character but whitespace 7 | * `\w` - matches A-Z, a-z, 0-9, and _ 8 | * `\W` - matches any character but A-Z, a-z, 0-9, and _ 9 | * `\t` - matches a tab character 10 | * `\n` - matches a newline character 11 | * This can be used in the template to print a newline 12 | 13 | ##### Character Sets 14 | * `[ab]` - matches any one character within the brackets 15 | * `[^ab]` - matches any character except those in the brackets 16 | 17 | ##### Alternation 18 | * `a|b` - matches the expression on the right or the expression on the left 19 | 20 | ##### Boundaries 21 | * `^` - matches the beginning of input 22 | * Also matches the beginning of the line when 'Multiline' is checked 23 | * `$` - matches the end of input 24 | * Also matches the end of the line before the line break character when 'Multiline' is checked 25 | * `\b` - matches word boundaries 26 | * `\B` - matches non-word boundaries 27 | 28 | ##### Grouping and Back References 29 | * `(a)` - matches the expression within and remembers the match 30 | * `\n` - matches the *n*th parenthesized expression, where *n* is a positive integer 31 | * This can be used in the template to print the *n*th parenthesized expression 32 | * `(?:a)` - matches the expression within and doesn't remember the match 33 | 34 | ##### Quantifiers 35 | * `a*` - matches the expression 0 or more times 36 | * `a+` - matches the expression 1 or more times 37 | * `a?` - matches the expression 0 or 1 times 38 | * `a{n}` - matches the expression *n* times 39 | * `a{n,}`- matches the expression *n* or more times 40 | * `a{n,m}` - matches the expression *n* to *m* times 41 | 42 | For details, examples, and advanced concepts, visit [this documentation.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Special_characters_meaning_in_regular_expressions) 43 | 44 | Note: *a* and *b* in the examples represent any expression 45 | -------------------------------------------------------------------------------- /src/options/options.js: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.css"; 2 | import "bootstrap/dist/js/bootstrap.js"; 3 | import "../css/style.css"; 4 | import "./options.css"; 5 | 6 | var profilesList = document.querySelector("#profilesList"); 7 | 8 | document.addEventListener("DOMContentLoaded", displayProfiles); 9 | 10 | 11 | 12 | function saveButtonClicked(e){ 13 | const id = e.target.getAttribute("storageID"); 14 | const profileName = document.querySelector('[storageID="'+ id + '"][name="profileNameInput"]'); 15 | const regex = document.querySelector('[storageID="'+ id + '"][name="regex"]'); 16 | const Global = document.querySelector('[storageID="'+ id + '"][name="Global"]'); 17 | const CaseInsensitive = document.querySelector('[storageID="'+ id + '"][name="Case-insensitive"]'); 18 | const Multiline = document.querySelector('[storageID="'+ id + '"][name="Multiline"]'); 19 | const IgnoreHTML = document.querySelector('[storageID="'+ id + '"][name="IgnoreHTML"]'); 20 | const template = document.querySelector('[storageID="'+ id + '"][name="template"]'); 21 | 22 | updateProfile(id, profileName.value, regex.value, Global.checked, CaseInsensitive.checked, Multiline.checked, template.value, IgnoreHTML.checked); 23 | } 24 | function deleteButtonClicked(e){ 25 | const id = e.target.getAttribute("storageID"); 26 | deleteProfile(id); 27 | } 28 | 29 | 30 | function displayProfiles() { 31 | var store = browser.storage.local.get({ 32 | profiles: [] 33 | }); 34 | store.then(function(results) { 35 | var profiles = results.profiles; 36 | console.log(profiles); 37 | for (var i = 0; i < profiles.length; i++) { 38 | console.log(profiles[i]); 39 | addProfileToAList(profiles[i]); 40 | 41 | } 42 | } , console.log); 43 | } 44 | function addProfileToAList(profile) { 45 | 46 | // fix if the profile doesn't have IgnoreHTML 47 | profile.IgnoreHTML = profile.IgnoreHTML ? true : false; 48 | 49 | var listItem = document.createElement("div"); 50 | listItem.setAttribute("class", "col-md-6 col-xs-12"); 51 | listItem.innerHTML = '
\ 52 |
\ 53 |
\ 54 |
\ 55 | \ 56 | \ 57 |
\ 58 |
\ 59 | \ 60 |
\ 61 | /\ 62 | \ 63 | /\ 64 |
\ 65 |
\ 66 |
\ 67 | \ 68 | \ 69 | \ 70 | \ 71 |
\ 72 |
\ 73 | \ 74 | \ 75 |
\ 76 |
\ 77 | \ 78 | \ 79 |
\ 80 |
'; 81 | listItem.querySelector('.panel-heading').innerHTML = profile.name; // profile name Heading 82 | listItem.querySelector('.profileNameInput').value = profile.name; // profile name Input 83 | listItem.querySelector('.regex').value = profile.regex; // regex Input 84 | listItem.querySelector('.template').value = profile.template; // template Input 85 | 86 | profilesList.appendChild(listItem); 87 | 88 | var saveButtons = document.getElementsByClassName("saveButton"); 89 | saveButtons[saveButtons.length - 1].addEventListener("click", saveButtonClicked); 90 | 91 | var deleteButtons = document.getElementsByClassName("deleteButton"); 92 | deleteButtons[deleteButtons.length - 1].addEventListener("click", deleteButtonClicked); 93 | } 94 | 95 | 96 | function deleteProfile(profileId){ 97 | var store = browser.storage.local.get({ 98 | profiles: [] 99 | }).then((results) =>{ 100 | var profiles = results.profiles; 101 | 102 | for (var i = 0; i < profiles.length; i++) { 103 | if (profileId == profiles[i].id) { 104 | profiles.splice(i , 1); 105 | updateProfiles(profiles); 106 | break; 107 | } 108 | } 109 | }).catch(()=>{ 110 | 111 | }); 112 | } 113 | 114 | function updateProfile(profileId, newName, newRegex, newGlobal, newCase, newMultiline, newTemplate, newIgnoreHTML){ 115 | var store = browser.storage.local.get({ 116 | profiles: [] 117 | }).then((results) =>{ 118 | var profiles = results.profiles; 119 | 120 | for (var i = 0; i < profiles.length; i++) { 121 | if (profileId == profiles[i].id) { 122 | profiles[i].name = newName; 123 | profiles[i].regex = newRegex; 124 | profiles[i].globalFlag = newGlobal; 125 | profiles[i].caseInsensitiveFlag = newCase; 126 | profiles[i].multilineFlag = newMultiline; 127 | profiles[i].template = newTemplate; 128 | profiles[i].IgnoreHTML = newIgnoreHTML; 129 | 130 | 131 | updateProfiles(profiles); 132 | break; 133 | } 134 | } 135 | }).catch(()=>{ 136 | 137 | }); 138 | } 139 | 140 | 141 | 142 | function updateProfiles(newProfiles) { 143 | var store = browser.storage.local.set({ 144 | profiles: newProfiles 145 | }).then( 146 | () => { 147 | location.reload(); 148 | } 149 | ); 150 | } 151 | -------------------------------------------------------------------------------- /src/icons/RegexSearch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 31 | 32 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 85 | 88 | 94 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/icons/RegexSearch popup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 31 | 32 | 55 | 57 | 58 | 60 | image/svg+xml 61 | 63 | 64 | 65 | 66 | 67 | 72 | 76 | 82 | 87 | 88 | 91 | 97 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/popup/RegexSearch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 | / 12 | 13 | / 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 40 | 45 |
46 | 47 |
48 |
49 |
50 | 51 |

52 | 53 |
54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 | 62 | 107 | 108 | 109 | 189 | 190 | 191 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /src/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /src/popup/RegexSearch.js: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.css"; 2 | import "bootstrap/dist/js/bootstrap.js"; 3 | import "../css/style.css"; 4 | import "./RegexSearch.css"; 5 | 6 | // Get what we need from HTML 7 | var searchButton = document.getElementById("searchButton"); 8 | var highlightButton = document.getElementById("highlightButton"); 9 | var nextButton = document.getElementById("nextButton"); 10 | var previousButton = document.getElementById("previousButton"); 11 | var resetButton = document.getElementById("resetButton"); 12 | var copyResultButton = document.getElementById("copyResultButton"); 13 | var saveButton_modal = document.getElementById("saveButton_modal"); 14 | var profilesList = document.getElementById("profilesList"); 15 | var profileNameInput_modal = document.getElementById("profileNameInput_modal"); 16 | var regexInput = document.getElementById("regexInput"); 17 | var regexInput_modal = document.getElementById("regexInput_modal"); 18 | var templateInput = document.getElementById("templateInput"); 19 | var templateInput_modal = document.getElementById("templateInput_modal"); 20 | var resultTextarea = document.getElementById("resultTextarea"); 21 | var globalCheckbox = document.getElementById("globalCheckbox"); 22 | var globalCheckbox_modal = document.getElementById("globalCheckbox_modal"); 23 | var caseInsensitiveCheckbox = document.getElementById("caseInsensitiveCheckbox"); 24 | var caseInsensitiveCheckbox_modal = document.getElementById("caseInsensitiveCheckbox_modal"); 25 | var multilineCheckbox = document.getElementById("multilineCheckbox"); 26 | var multilineCheckbox_modal = document.getElementById("multilineCheckbox_modal"); 27 | var IgnoreHTMLCheckbox = document.getElementById("IgnoreHTMLCheckbox"); 28 | var IgnoreHTMLCheckbox_modal = document.getElementById("IgnoreHTMLCheckbox_modal"); 29 | var matchesCount = document.getElementById("matchesCount"); 30 | var colorButton = document.getElementById("colorButton"); 31 | var customColorRadio = document.getElementById("customColor"); 32 | var customColorInput = document.getElementById("customColorInput"); 33 | var customBorderColorRadio = document.getElementById("customBorderColor"); 34 | var customBorderColorInput = document.getElementById("customBorderColorInput"); 35 | var updateColorButton = document.getElementById("updateColor"); 36 | var smallFormCheckbox = document.getElementById("smallFormCheckbox"); 37 | var fullFormCheckbox = document.getElementById("fullFormCheckbox"); 38 | var shiftHeld = false; 39 | var highlightColor = "yellow"; 40 | var highlightBorderColor = "magenta"; 41 | var selectedItemIndex = -1; 42 | // Get last data from storage so user doesn't have to type it again and update saving model 43 | getCurrent(); 44 | // Get profiles from storage 45 | displayProfiles(); 46 | 47 | //register the Listener 48 | browser.tabs.executeScript(null, { 49 | file: "/content_script.js" 50 | }); 51 | 52 | 53 | regexInput.addEventListener('keyup', clickSearchButtonOnEnter) 54 | templateInput.addEventListener('keyup', clickSearchButtonOnEnter) 55 | function clickSearchButtonOnEnter(event){ 56 | if (event.keyCode === 16) { // Shift key 57 | shiftHeld = false; 58 | } 59 | if (event.keyCode === 13){ // Enter key 60 | if(this.id === regexInput.id && shiftHeld){ 61 | highlightButton.click(); 62 | return; 63 | } 64 | searchButton.click() 65 | } 66 | } 67 | 68 | regexInput.addEventListener('keydown', event => { 69 | if (event.keyCode === 16) { // Shift key 70 | shiftHeld = true; 71 | } 72 | }); 73 | 74 | // When search Button clicked 75 | searchButton.addEventListener("click", (e) => { 76 | 77 | // Add this script to the current tab , first arguments (null) gives the current tab 78 | browser.tabs.executeScript(null, { 79 | file: "/content_script.js" 80 | }); 81 | 82 | // Get current tab to connect to the Script we provided on the code above 83 | var gettingActiveTab = browser.tabs.query({ 84 | active: true, 85 | currentWindow: true 86 | }); 87 | gettingActiveTab.then((tabs) => { 88 | browser.tabs.sendMessage(tabs[0].id, { 89 | regex: regexInput.value, 90 | flags: getFlags(), 91 | template: templateInput.value, 92 | action: "search", 93 | ignoreHTML: IgnoreHTMLCheckbox.checked 94 | }).then(getResponse); 95 | }); 96 | 97 | // callback function when we get the response from the script on the tab 98 | function getResponse(handleResponse, handleError) { 99 | // reset the result textarea 100 | resultTextarea.value = ""; 101 | var matches = handleResponse.results; 102 | 103 | for (var i = 0; i < matches.length; i++) { 104 | resultTextarea.value += matches[i]; 105 | } 106 | 107 | if (matches.length == 0 || matches == undefined){ 108 | matchesCount.innerText = "No Matches Found"; 109 | } else { 110 | matchesCount.innerText = `${matches.length} match${ matches.length > 1 ? 'es' : '' } found`; 111 | } 112 | // store current values in the storage so user doesn't have to type again when he comes back to popup 113 | storeCurrent(); 114 | } 115 | }); 116 | 117 | highlightButton.addEventListener("click", (e) => { 118 | // Add this script to the current tab , first arguments (null) gives the current tab 119 | browser.tabs.executeScript(null, { 120 | file: "/content_script.js" 121 | }); 122 | 123 | // Get current tab to connect to the Script we provided on the code above 124 | var gettingActiveTab = browser.tabs.query({ 125 | active: true, 126 | currentWindow: true 127 | }); 128 | gettingActiveTab.then((tabs) => { 129 | browser.tabs.sendMessage(tabs[0].id, { 130 | regex: regexInput.value, 131 | flags: getFlags(), 132 | action: "highlight", 133 | ignoreHTML: IgnoreHTMLCheckbox.checked, 134 | highlightColor: highlightColor, 135 | highlightBorderColor: highlightBorderColor 136 | }); 137 | }); 138 | storeCurrent(); 139 | }); 140 | 141 | nextButton.addEventListener("click", (e) => { 142 | 143 | // Add this script to the current tab , first arguments (null) gives the current tab 144 | browser.tabs.executeScript(null, { 145 | file: "/content_script.js" 146 | }); 147 | 148 | // Get current tab to connect to the Script we provided on the code above 149 | var gettingActiveTab = browser.tabs.query({ 150 | active: true, 151 | currentWindow: true 152 | }); 153 | gettingActiveTab.then((tabs) => { 154 | browser.tabs.sendMessage(tabs[0].id, { 155 | regex: regexInput.value, 156 | flags: getFlags(), 157 | template: templateInput.value, 158 | action: "next", 159 | ignoreHTML: IgnoreHTMLCheckbox.checked, 160 | highlightColor: highlightColor, 161 | highlightBorderColor: highlightBorderColor, 162 | selectedItemIndex:selectedItemIndex 163 | }).then(getResponse); 164 | }); 165 | 166 | // callback function when we get the response from the script on the tab 167 | function getResponse(handleResponse, handleError) { 168 | try { 169 | var matches = handleResponse.results; 170 | if (handleResponse.selectedItemIndex > 0) { 171 | selectedItemIndex = handleResponse.selectedItemIndex 172 | storeCurrent(); 173 | return 174 | } 175 | selectedItemIndex = handleResponse.selectedItemIndex 176 | // reset the result textarea 177 | if (selectedItemIndex <= 0) { 178 | resultTextarea.value = ""; 179 | for (var i = 0; i < matches.length; i++) { 180 | resultTextarea.value += matches[i] + "\n"; 181 | } 182 | if (matches.length == 0 || matches == undefined) { 183 | matchesCount.innerText = "No Matches Found"; 184 | } else { 185 | matchesCount.innerText = `${matches.length} match${matches.length > 1 ? 'es' : ''} found`; 186 | } 187 | } 188 | // store current values in the storage so user doesn't have to type again when he comes back to popup 189 | storeCurrent(); 190 | } 191 | catch (ex) { 192 | window.eval(`console.error(ex)`); 193 | } 194 | } 195 | }); 196 | 197 | previousButton.addEventListener("click", (e) => { 198 | 199 | // Add this script to the current tab , first arguments (null) gives the current tab 200 | browser.tabs.executeScript(null, { 201 | file: "/content_script.js" 202 | }); 203 | 204 | // Get current tab to connect to the Script we provided on the code above 205 | var gettingActiveTab = browser.tabs.query({ 206 | active: true, 207 | currentWindow: true 208 | }); 209 | gettingActiveTab.then((tabs) => { 210 | browser.tabs.sendMessage(tabs[0].id, { 211 | regex: regexInput.value, 212 | flags: getFlags(), 213 | template: templateInput.value, 214 | action: "previous", 215 | ignoreHTML: IgnoreHTMLCheckbox.checked, 216 | highlightColor: highlightColor, 217 | highlightBorderColor: highlightBorderColor, 218 | selectedItemIndex:selectedItemIndex 219 | }).then(getResponse); 220 | }); 221 | 222 | // callback function when we get the response from the script on the tab 223 | function getResponse(handleResponse, handleError) { 224 | try { 225 | var matches = handleResponse.results; 226 | if (handleResponse.selectedItemIndex != matches.length -1) { 227 | selectedItemIndex = handleResponse.selectedItemIndex; 228 | storeCurrent(); 229 | return 230 | } 231 | selectedItemIndex = handleResponse.selectedItemIndex 232 | // reset the result textarea 233 | resultTextarea.value = ""; 234 | for (var i = 0; i < matches.length; i++) { 235 | resultTextarea.value += matches[i] + "\n"; 236 | } 237 | if (matches.length == 0 || matches == undefined) { 238 | matchesCount.innerText = "No Matches Found"; 239 | } else { 240 | matchesCount.innerText = `${matches.length} match${matches.length > 1 ? 'es' : ''} found`; 241 | } 242 | // store current values in the storage so user doesn't have to type again when he comes back to popup 243 | storeCurrent(); 244 | } 245 | catch (ex) { 246 | window.eval(`console.error(ex)`); 247 | } 248 | } 249 | }); 250 | 251 | 252 | // A function returns REGEX flags depending on user choices 253 | function getFlags() { 254 | var flags = ""; // start with empty flags 255 | if (globalCheckbox.checked) { 256 | flags += "g"; 257 | } 258 | if (caseInsensitiveCheckbox.checked) { 259 | flags += "i"; 260 | } 261 | if (multilineCheckbox.checked) { 262 | flags += "m"; 263 | } 264 | return flags; 265 | } 266 | 267 | 268 | // When save button (the one in the saving model) have been pressed 269 | saveButton_modal.addEventListener("click", (e) => { 270 | // Get data 271 | var id = getID(); 272 | var name = profileNameInput_modal.value; 273 | var regex = regexInput_modal.value; 274 | var globalFlag = globalCheckbox_modal.checked; 275 | var caseInsensitiveFlag = caseInsensitiveCheckbox_modal.checked; 276 | var multilineFlag = multilineCheckbox_modal.checked; 277 | var ignoreHTML = IgnoreHTMLCheckbox_modal.checked; 278 | var template = templateInput_modal.value; 279 | 280 | // Check for the name , we need it to save the profile 281 | if (name.length == 0) { 282 | $("#name_alert_modal").show(); 283 | return; 284 | } 285 | // make new object 286 | profile = new profile(id , name, regex, globalFlag, caseInsensitiveFlag, multilineFlag, template, ignoreHTML); 287 | // add the profile to the storage 288 | addProfile(profile); 289 | 290 | }); 291 | 292 | 293 | // When the user clicks on profile we need to load it 294 | profilesList.addEventListener("click", (e) => { 295 | const storageID = e.target.getAttribute("storageID"); 296 | if (storageID == 'manage'){ 297 | browser.runtime.openOptionsPage(); 298 | } else { 299 | getProfile(storageID); 300 | } 301 | }); 302 | 303 | // Copy Result button event 304 | copyResultButton.addEventListener("click", (e) => { 305 | resultTextarea.select(); 306 | document.execCommand("Copy"); 307 | }); 308 | 309 | 310 | // sync modal with main inputs and store it for the next session if the user exits by mistake 311 | regexInput.addEventListener("change", (e) => { 312 | updateSaveModal(); 313 | storeCurrent(); 314 | selectedItemIndex = -1 315 | }); 316 | templateInput.addEventListener("change", (e) => { 317 | updateSaveModal(); 318 | storeCurrent(); 319 | selectedItemIndex = -1 320 | }); 321 | globalCheckbox.addEventListener("change", (e) => { 322 | updateSaveModal(); 323 | storeCurrent(); 324 | selectedItemIndex = -1 325 | }); 326 | caseInsensitiveCheckbox.addEventListener("change", (e) => { 327 | updateSaveModal(); 328 | storeCurrent(); 329 | selectedItemIndex = -1 330 | }); 331 | multilineCheckbox.addEventListener("change", (e) => { 332 | updateSaveModal(); 333 | storeCurrent(); 334 | selectedItemIndex = -1 335 | }); 336 | IgnoreHTMLCheckbox.addEventListener("change", (e) => { 337 | updateSaveModal(); 338 | storeCurrent(); 339 | selectedItemIndex = -1 340 | }); 341 | resultTextarea.addEventListener("change", (e) => { 342 | storeCurrent(); 343 | selectedItemIndex = -1 344 | }); 345 | 346 | smallFormCheckbox.addEventListener("change", (e) => { 347 | smallFormCheckboxChange(e); 348 | }); 349 | 350 | function smallFormCheckboxChange(e){ 351 | if (smallFormCheckbox.checked) { 352 | document.querySelectorAll(".small-form").forEach((el) => { 353 | el.classList.remove("small-form") 354 | el.classList.add("big-form") 355 | }) 356 | document.querySelectorAll(".full-form").forEach((el) => { 357 | el.classList.remove("full-form") 358 | el.classList.add("small-form") 359 | }) 360 | fullFormCheckbox.checked=true; 361 | } 362 | storeCurrent(); 363 | } 364 | 365 | fullFormCheckbox.addEventListener("change", (e) => { 366 | fullFormCheckboxChange(e); 367 | }); 368 | 369 | function fullFormCheckboxChange(e){ 370 | if (!fullFormCheckbox.checked) { 371 | document.querySelectorAll(".small-form").forEach((el) => { 372 | el.classList.remove("small-form") 373 | el.classList.add("full-form") 374 | }) 375 | document.querySelectorAll(".big-form").forEach((el) => { 376 | el.classList.remove("big-form") 377 | el.classList.add("small-form") 378 | console.log(el) 379 | }) 380 | } 381 | smallFormCheckbox.checked =false; 382 | storeCurrent(); 383 | } 384 | 385 | // Reset Button Event 386 | resetButton.addEventListener("click", (e) => { 387 | regexInput.value = ""; 388 | templateInput.value = ""; 389 | globalCheckbox.checked = false; 390 | caseInsensitiveCheckbox.checked = false; 391 | multilineCheckbox.checked = false; 392 | IgnoreHTMLCheckbox.checked = false; 393 | resultTextarea.value = ""; 394 | matchesCount.innerText = ""; 395 | selectedItemIndex = -1; 396 | smallFormCheckbox.checked = false; 397 | // Remove the highlights on the page 398 | 399 | // Add this script to the current tab , first arguments (null) gives the current tab 400 | browser.tabs.executeScript(null, { 401 | file: "/content_script.js" 402 | }); 403 | 404 | // Get current tab to connect to the Script we provided on the code above 405 | browser.tabs.query({ 406 | active: true, 407 | currentWindow: true 408 | }).then((tabs) => { // Send the message to remove the highlights 409 | browser.tabs.sendMessage(tabs[0].id, { 410 | action: "remove-highlights" 411 | }); 412 | }); 413 | 414 | // to reset the storage also 415 | storeCurrent(); 416 | }); 417 | 418 | // Open Color Highlight Modal 419 | colorButton.addEventListener("click", (e) => { 420 | if (highlightColor && highlightColor[0] == "#") { 421 | customColorRadio.checked = true; 422 | customColorInput.value = highlightColor.substring(1); 423 | } else { 424 | document.querySelector("input[name='color'][value='" + highlightColor + "']").checked = true; 425 | } 426 | if (highlightBorderColor && highlightBorderColor[0] == "#") { 427 | customBorderColorRadio.checked = true; 428 | customBorderColorInput.value = highlightBorderColor.substring(1); 429 | } else { 430 | document.querySelector("input[name='borderColor'][value='" + highlightBorderColor + "'").checked = true; 431 | } 432 | }); 433 | 434 | // Update Color Button Event 435 | updateColorButton.addEventListener("click", (e) => { 436 | highlightColor = document.querySelector("input[name='color']:checked").value; 437 | if(highlightColor === "custom") 438 | highlightColor = "#" + customColorInput.value; 439 | highlightBorderColor = document.querySelector("input[name='borderColor']:checked").value; 440 | if(highlightBorderColor === "custom") 441 | highlightBorderColor = "#" + customBorderColorInput.value; 442 | storeHighlightColor(); 443 | storeHighlightBorderColor(); 444 | }); 445 | 446 | // Update save model inputs 447 | function updateSaveModal() { 448 | regexInput_modal.value = regexInput.value; 449 | templateInput_modal.value = templateInput.value; 450 | globalCheckbox_modal.checked = globalCheckbox.checked; 451 | caseInsensitiveCheckbox_modal.checked = caseInsensitiveCheckbox.checked; 452 | multilineCheckbox_modal.checked = multilineCheckbox.checked; 453 | IgnoreHTMLCheckbox_modal.checked = IgnoreHTMLCheckbox.checked; 454 | } 455 | // === Storage functions === 456 | 457 | // store current data for the next session if the user exits by mistake 458 | function storeCurrent() { 459 | let currentData = { 460 | regexInput: regexInput.value, 461 | templateInput: templateInput.value, 462 | globalCheckbox: globalCheckbox.checked, 463 | caseInsensitiveCheckbox: caseInsensitiveCheckbox.checked, 464 | multilineCheckbox: multilineCheckbox.checked, 465 | IgnoreHTMLCheckbox: IgnoreHTMLCheckbox.checked, 466 | resultTextarea: resultTextarea.value, 467 | selectedItemIndex:selectedItemIndex, 468 | smallForm: smallFormCheckbox.checked 469 | }; 470 | let store = browser.storage.local.set({ 471 | currentData 472 | }); 473 | store.then(onError, onError); 474 | } 475 | 476 | // Get last user input, called when we start the script 477 | function getCurrent() { 478 | let store = browser.storage.local.get({ 479 | currentData: { 480 | regexInput: "", 481 | templateInput: "", 482 | globalCheckbox: false, 483 | caseInsensitiveCheckbox: false, 484 | multilineCheckbox: false, 485 | IgnoreHTMLCheckbox: false, 486 | resultTextarea: "", 487 | selectedItemIndex:-1, 488 | smallForm: false, 489 | }, 490 | 491 | highlightColor: "yellow", 492 | highlightBorderColor: "magenta" 493 | }); 494 | 495 | store.then(function(results) { 496 | regexInput.value = results.currentData.regexInput; 497 | templateInput.value = results.currentData.templateInput; 498 | globalCheckbox.checked = results.currentData.globalCheckbox; 499 | caseInsensitiveCheckbox.checked = results.currentData.caseInsensitiveCheckbox; 500 | multilineCheckbox.checked = results.currentData.multilineCheckbox; 501 | IgnoreHTMLCheckbox.checked = results.currentData.IgnoreHTMLCheckbox; 502 | resultTextarea.value = results.currentData.resultTextarea; 503 | selectedItemIndex = results.currentData.selectedItemIndex; 504 | highlightColor = results.highlightColor; 505 | highlightBorderColor= results.highlightBorderColor, 506 | smallFormCheckbox.checked = results.currentData.smallForm; 507 | smallFormCheckboxChange("") 508 | updateSaveModal(); 509 | }, onError); 510 | } 511 | 512 | 513 | // add profile to the storage then update the profiles in the list 514 | function addProfile(profile) { 515 | let store = browser.storage.local.get({ 516 | profiles: [] 517 | }); 518 | store.then(function(results) { 519 | var profiles = results.profiles; 520 | profiles.push(profile); 521 | let store = browser.storage.local.set({ 522 | profiles 523 | }); 524 | store.then(null, onError); 525 | displayProfiles(); 526 | }); 527 | } 528 | 529 | // Get the profiles from storage then display them in the list 530 | function displayProfiles() { 531 | let store = browser.storage.local.get({ 532 | profiles: [] 533 | }); 534 | store.then(function(results) { 535 | var profiles = results.profiles; 536 | 537 | for (var i = 0; i < profiles.length; i++) { 538 | addProfileToAList(profiles[i]); 539 | } 540 | }); 541 | } 542 | 543 | // simple function to append a profile to the list 544 | function addProfileToAList(profile) { 545 | var listItem = document.createElement("li"); 546 | listItem.innerHTML = '' + profile.name + ''; 547 | profilesList.appendChild(listItem); 548 | } 549 | 550 | // Get a profile from storage using its name then add its data to the input fields 551 | function getProfile(profileId) { 552 | let store = browser.storage.local.get({ 553 | profiles: [] 554 | }); 555 | store.then(function(results) { 556 | var profiles = results.profiles; 557 | 558 | for (var i = 0; i < profiles.length; i++) { 559 | if (profileId == profiles[i].id) { 560 | regexInput.value = profiles[i].regex; 561 | templateInput.value = profiles[i].template; 562 | globalCheckbox.checked = profiles[i].globalFlag; 563 | caseInsensitiveCheckbox.checked = profiles[i].caseInsensitiveFlag; 564 | multilineCheckbox.checked = profiles[i].multilineFlag; 565 | IgnoreHTMLCheckbox.checked = profiles[i].IgnoreHTML ? true : false; 566 | break; 567 | } 568 | } 569 | }, onError); 570 | } 571 | 572 | 573 | function storeHighlightColor() { 574 | browser.storage.local.set({ highlightColor }) 575 | .catch(onError); 576 | } 577 | function storeHighlightBorderColor () { 578 | browser.storage.local.set({ highlightBorderColor }) 579 | .catch(onError); 580 | } 581 | 582 | 583 | // to log errors :D 584 | function onError(err) { 585 | console.error(err); 586 | } 587 | 588 | 589 | // Profile object constructer 590 | function profile(id , name, regex, globalFlag, caseInsensitiveFlag, multilineFlag, template, IgnoreHTML) { 591 | this.id = id; 592 | this.name = name; 593 | this.regex = regex; 594 | this.globalFlag = globalFlag; 595 | this.caseInsensitiveFlag = caseInsensitiveFlag; 596 | this.multilineFlag = multilineFlag; 597 | this.IgnoreHTML = IgnoreHTML; 598 | this.template = template; 599 | } 600 | function getID() { 601 | return Math.floor((Math.random() * 99999999999) + 1); 602 | } 603 | -------------------------------------------------------------------------------- /src/content_scripts/page_search.js: -------------------------------------------------------------------------------- 1 | function search(request, sender, sendResponse) { 2 | try { 3 | if (request.action == "search") { 4 | var regex = new RegExp(request.regex, request.flags); 5 | var matches = []; 6 | var content = request.ignoreHTML ? document.body.innerText : document.body.innerHTML; 7 | var match; 8 | 9 | // if its global means we have more than one result, we need to loop 10 | if (regex.flags.includes("g")) { 11 | while (match = regex.exec(content)) { 12 | matches.push(formatTemplate(match, request.template)); 13 | } 14 | } else { 15 | if (match = regex.exec(content)) { 16 | matches.push(formatTemplate(match, request.template)); 17 | } 18 | } 19 | // send response to popup script 20 | sendResponse({ results: matches }); 21 | } else if (request.action == "highlight") { 22 | var previousHighlighters = document.querySelectorAll(".regexSearchHighlighter"); 23 | 24 | previousHighlighters.forEach((highlight) => { 25 | highlight.style.backgroundColor = "transparent"; 26 | highlight.classList.remove("regexSearchHighlighter"); 27 | }); 28 | 29 | var regex = new RegExp("(" + request.regex + ")", request.flags); 30 | 31 | findAndReplaceDOMText(document.body, { 32 | find: regex, 33 | wrap: 'span', 34 | wrapClass: "regexSearchHighlighter" 35 | }); 36 | 37 | var newHighlighters = document.querySelectorAll(".regexSearchHighlighter"); 38 | newHighlighters.forEach((highlight) => { 39 | highlight.style.backgroundColor = request.highlightColor; 40 | }); 41 | } else if (request.action == "next") { 42 | 43 | var regex = new RegExp(request.regex, request.flags); 44 | var matches = []; 45 | var selectedItemIndex = request.selectedItemIndex; 46 | if (selectedItemIndex == -1) { 47 | findAndReplaceDOMText(document.body, { 48 | find: regex, 49 | wrap: 'span', 50 | wrapClass: "regexSearchMatch" 51 | }); 52 | } 53 | var elementMatches = document.querySelectorAll(".regexSearchMatch"); 54 | if (selectedItemIndex == -1) { 55 | for (let index = 0; index < elementMatches.length; index++) { 56 | matches.push(elementMatches[index].innerText) 57 | } 58 | } 59 | //remove previous highlight if exist 60 | if (selectedItemIndex>=0 && elementMatches.length > selectedItemIndex+1){ 61 | elementMatches[selectedItemIndex].style.backgroundColor = "transparent"; 62 | elementMatches[selectedItemIndex].classList.remove("regexSearchHighlighter"); 63 | el.style.border="" 64 | } 65 | //highlight and scroll down to the item 66 | if (elementMatches.length > selectedItemIndex+1){ 67 | selectedItemIndex = selectedItemIndex + 1; 68 | el = elementMatches[selectedItemIndex]; 69 | el.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"}); 70 | el.style.backgroundColor = request.highlightColor; 71 | el.style.border=`2px solid ${request.highlightBorderColor}` 72 | 73 | } 74 | sendResponse({ results: matches, selectedItemIndex:selectedItemIndex }); 75 | } 76 | else if (request.action == "previous") { 77 | var regex = new RegExp(request.regex, request.flags); 78 | var matches = []; 79 | var selectedItemIndex = request.selectedItemIndex; 80 | if (selectedItemIndex == -1) { 81 | findAndReplaceDOMText(document.body, { 82 | find: regex, 83 | wrap: 'span', 84 | wrapClass: "regexSearchMatch" 85 | }); 86 | } 87 | var elementMatches = document.querySelectorAll(".regexSearchMatch"); 88 | if (selectedItemIndex == -1) { 89 | for (let index = 0; index < elementMatches.length; index++) { 90 | matches.push(elementMatches[index].innerText) 91 | } 92 | selectedItemIndex=elementMatches.length 93 | } 94 | //remove previous highlight if exist 95 | if (selectedItemIndex=0){ 96 | elementMatches[selectedItemIndex].style.backgroundColor = "transparent"; 97 | elementMatches[selectedItemIndex].classList.remove("regexSearchHighlighter"); 98 | el.style.border="" 99 | } 100 | //highlight and scroll down to the item 101 | if (selectedItemIndex-1>=0){ 102 | selectedItemIndex = selectedItemIndex - 1; 103 | el = elementMatches[selectedItemIndex]; 104 | el.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest"}); 105 | el.style.backgroundColor = request.highlightColor; 106 | el.style.border=`2px solid ${request.highlightBorderColor}` 107 | 108 | } 109 | sendResponse({ results: matches, selectedItemIndex:selectedItemIndex }); 110 | } 111 | } 112 | catch (ex) { 113 | console.error(ex); 114 | } 115 | finally { 116 | browser.runtime.onMessage.removeListener(search); 117 | } 118 | } 119 | 120 | browser.runtime.onMessage.addListener(search); 121 | 122 | 123 | // a function to use the provided template 124 | function formatTemplate(match , template){ 125 | if (template == ""){ 126 | return match[0]; 127 | } 128 | template = template.replace(/\$(\d+|&|`|')|\\(n|r)/g, function($0, tkn, newLine) { 129 | var token; 130 | switch(tkn) { 131 | case '&': 132 | token = match[0]; 133 | break; 134 | case '$': 135 | token = '$'; 136 | break; 137 | case '`': 138 | token = match.input.substring(0, match.index); 139 | break; 140 | case '\'': 141 | token = match.input.substring(match.index); 142 | break; 143 | default: 144 | token = match[+tkn] || ''; 145 | } 146 | if (newLine == 'n' || newLine == 'r') { 147 | return "\n"; 148 | } 149 | return token; 150 | }); 151 | return template 152 | } 153 | 154 | 155 | 156 | /** 157 | * findAndReplaceDOMText v 0.4.6 158 | * @author James Padolsey http://james.padolsey.com 159 | * @license http://unlicense.org/UNLICENSE 160 | * 161 | * Matches the text of a DOM node against a regular expression 162 | * and replaces each match (or node-separated portions of the match) 163 | * in the specified element. 164 | */ 165 | (function (root, factory) { 166 | if (typeof module === 'object' && module.exports) { 167 | // Node/CommonJS 168 | module.exports = factory(); 169 | } else if (typeof define === 'function' && define.amd) { 170 | // AMD. Register as an anonymous module. 171 | define(factory); 172 | } else { 173 | // Browser globals 174 | root.findAndReplaceDOMText = factory(); 175 | } 176 | }(this, function factory() { 177 | 178 | var PORTION_MODE_RETAIN = 'retain'; 179 | var PORTION_MODE_FIRST = 'first'; 180 | 181 | var doc = document; 182 | var hasOwn = {}.hasOwnProperty; 183 | 184 | function escapeRegExp(s) { 185 | return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 186 | } 187 | 188 | function exposed() { 189 | // Try deprecated arg signature first: 190 | return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments); 191 | } 192 | 193 | function deprecated(regex, node, replacement, captureGroup, elFilter) { 194 | if ((node && !node.nodeType) && arguments.length <= 2) { 195 | return false; 196 | } 197 | var isReplacementFunction = typeof replacement == 'function'; 198 | 199 | if (isReplacementFunction) { 200 | replacement = (function(original) { 201 | return function(portion, match) { 202 | return original(portion.text, match.startIndex); 203 | }; 204 | }(replacement)); 205 | } 206 | 207 | // Awkward support for deprecated argument signature (<0.4.0) 208 | var instance = findAndReplaceDOMText(node, { 209 | 210 | find: regex, 211 | 212 | wrap: isReplacementFunction ? null : replacement, 213 | replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'), 214 | 215 | prepMatch: function(m, mi) { 216 | 217 | // Support captureGroup (a deprecated feature) 218 | 219 | if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches'; 220 | 221 | if (captureGroup > 0) { 222 | var cg = m[captureGroup]; 223 | m.index += m[0].indexOf(cg); 224 | m[0] = cg; 225 | } 226 | 227 | m.endIndex = m.index + m[0].length; 228 | m.startIndex = m.index; 229 | m.index = mi; 230 | 231 | return m; 232 | }, 233 | filterElements: elFilter 234 | }); 235 | 236 | exposed.revert = function() { 237 | return instance.revert(); 238 | }; 239 | 240 | return true; 241 | } 242 | 243 | /** 244 | * findAndReplaceDOMText 245 | * 246 | * Locates matches and replaces with replacementNode 247 | * 248 | * @param {Node} node Element or Text node to search within 249 | * @param {RegExp} options.find The regular expression to match 250 | * @param {String|Element} [options.wrap] A NodeName, or a Node to clone 251 | * @param {String} [options.wrapClass] A classname to append to the wrapping element 252 | * @param {String|Function} [options.replace='$&'] What to replace each match with 253 | * @param {Function} [options.filterElements] A Function to be called to check whether to 254 | * process an element. (returning true = process element, 255 | * returning false = avoid element) 256 | */ 257 | function findAndReplaceDOMText(node, options) { 258 | return new Finder(node, options); 259 | } 260 | 261 | exposed.NON_PROSE_ELEMENTS = { 262 | br:1, hr:1, 263 | // Media / Source elements: 264 | script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1, 265 | // Input elements 266 | input:1, textarea:1, select:1, option:1, optgroup: 1, button:1 267 | }; 268 | 269 | exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = { 270 | 271 | // Elements that will not contain prose or block elements where we don't 272 | // want prose to be matches across element borders: 273 | 274 | // Block Elements 275 | address:1, article:1, aside:1, blockquote:1, dd:1, div:1, 276 | dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1, 277 | h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1, 278 | output:1, p:1, pre:1, section:1, ul:1, 279 | // Other misc. elements that are not part of continuous inline prose: 280 | br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1, 281 | // Media / Source elements: 282 | script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1, 283 | // Input elements 284 | input:1, textarea:1, select:1, option:1, optgroup:1, button:1, 285 | // Table related elements: 286 | table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1 287 | 288 | }; 289 | 290 | exposed.NON_INLINE_PROSE = function(el) { 291 | return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase()); 292 | }; 293 | 294 | // Presets accessed via `options.preset` when calling findAndReplaceDOMText(): 295 | exposed.PRESETS = { 296 | prose: { 297 | forceContext: exposed.NON_INLINE_PROSE, 298 | filterElements: function(el) { 299 | return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase()); 300 | } 301 | } 302 | }; 303 | 304 | exposed.Finder = Finder; 305 | 306 | /** 307 | * Finder -- encapsulates logic to find and replace. 308 | */ 309 | function Finder(node, options) { 310 | 311 | var preset = options.preset && exposed.PRESETS[options.preset]; 312 | 313 | options.portionMode = options.portionMode || PORTION_MODE_RETAIN; 314 | 315 | if (preset) { 316 | for (var i in preset) { 317 | if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) { 318 | options[i] = preset[i]; 319 | } 320 | } 321 | } 322 | 323 | this.node = node; 324 | this.options = options; 325 | 326 | // Enable match-preparation method to be passed as option: 327 | this.prepMatch = options.prepMatch || this.prepMatch; 328 | 329 | this.reverts = []; 330 | 331 | this.matches = this.search(); 332 | 333 | if (this.matches.length) { 334 | this.processMatches(); 335 | } 336 | 337 | } 338 | 339 | Finder.prototype = { 340 | 341 | /** 342 | * Searches for all matches that comply with the instance's 'match' option 343 | */ 344 | search: function() { 345 | 346 | var match; 347 | var matchIndex = 0; 348 | var offset = 0; 349 | var regex = this.options.find; 350 | var textAggregation = this.getAggregateText(); 351 | var matches = []; 352 | var self = this; 353 | 354 | regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex; 355 | 356 | matchAggregation(textAggregation); 357 | 358 | function matchAggregation(textAggregation) { 359 | for (var i = 0, l = textAggregation.length; i < l; ++i) { 360 | 361 | var text = textAggregation[i]; 362 | 363 | if (typeof text !== 'string') { 364 | // Deal with nested contexts: (recursive) 365 | matchAggregation(text); 366 | continue; 367 | } 368 | 369 | if (regex.global) { 370 | while (match = regex.exec(text)) { 371 | matches.push(self.prepMatch(match, matchIndex++, offset)); 372 | } 373 | } else { 374 | if (match = text.match(regex)) { 375 | matches.push(self.prepMatch(match, 0, offset)); 376 | } 377 | } 378 | 379 | offset += text.length; 380 | } 381 | } 382 | 383 | return matches; 384 | 385 | }, 386 | 387 | /** 388 | * Prepares a single match with useful meta info: 389 | */ 390 | prepMatch: function(match, matchIndex, characterOffset) { 391 | 392 | if (!match[0]) { 393 | throw new Error('findAndReplaceDOMText cannot handle zero-length matches'); 394 | } 395 | 396 | match.endIndex = characterOffset + match.index + match[0].length; 397 | match.startIndex = characterOffset + match.index; 398 | match.index = matchIndex; 399 | 400 | return match; 401 | }, 402 | 403 | /** 404 | * Gets aggregate text within subject node 405 | */ 406 | getAggregateText: function() { 407 | 408 | var elementFilter = this.options.filterElements; 409 | var forceContext = this.options.forceContext; 410 | 411 | return getText(this.node); 412 | 413 | /** 414 | * Gets aggregate text of a node without resorting 415 | * to broken innerText/textContent 416 | */ 417 | function getText(node) { 418 | 419 | if (node.nodeType === Node.TEXT_NODE) { 420 | return [node.data]; 421 | } 422 | 423 | if (elementFilter && !elementFilter(node)) { 424 | return []; 425 | } 426 | 427 | var txt = ['']; 428 | var i = 0; 429 | 430 | if (node = node.firstChild) do { 431 | 432 | if (node.nodeType === Node.TEXT_NODE) { 433 | txt[i] += node.data; 434 | continue; 435 | } 436 | 437 | var innerText = getText(node); 438 | 439 | if ( 440 | forceContext && 441 | node.nodeType === Node.ELEMENT_NODE && 442 | (forceContext === true || forceContext(node)) 443 | ) { 444 | txt[++i] = innerText; 445 | txt[++i] = ''; 446 | } else { 447 | if (typeof innerText[0] === 'string') { 448 | // Bridge nested text-node data so that they're 449 | // not considered their own contexts: 450 | // I.e. ['some', ['thing']] -> ['something'] 451 | txt[i] += innerText.shift(); 452 | } 453 | if (innerText.length) { 454 | txt[++i] = innerText; 455 | txt[++i] = ''; 456 | } 457 | } 458 | } while (node = node.nextSibling); 459 | 460 | return txt; 461 | 462 | } 463 | 464 | }, 465 | 466 | /** 467 | * Steps through the target node, looking for matches, and 468 | * calling replaceFn when a match is found. 469 | */ 470 | processMatches: function() { 471 | 472 | var matches = this.matches; 473 | var node = this.node; 474 | var elementFilter = this.options.filterElements; 475 | 476 | var startPortion, 477 | endPortion, 478 | innerPortions = [], 479 | curNode = node, 480 | match = matches.shift(), 481 | atIndex = 0, // i.e. nodeAtIndex 482 | matchIndex = 0, 483 | portionIndex = 0, 484 | doAvoidNode, 485 | nodeStack = [node]; 486 | 487 | out: while (true) { 488 | 489 | if (curNode.nodeType === Node.TEXT_NODE) { 490 | 491 | if (!endPortion && curNode.length + atIndex >= match.endIndex) { 492 | // We've found the ending 493 | // (Note that, in the case of a single portion, it'll be an 494 | // endPortion, not a startPortion.) 495 | endPortion = { 496 | node: curNode, 497 | index: portionIndex++, 498 | text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex), 499 | 500 | // If it's the first match (atIndex==0) we should just return 0 501 | indexInMatch: atIndex === 0 ? 0 : atIndex - match.startIndex, 502 | 503 | indexInNode: match.startIndex - atIndex, 504 | endIndexInNode: match.endIndex - atIndex, 505 | isEnd: true 506 | }; 507 | 508 | } else if (startPortion) { 509 | // Intersecting node 510 | innerPortions.push({ 511 | node: curNode, 512 | index: portionIndex++, 513 | text: curNode.data, 514 | indexInMatch: atIndex - match.startIndex, 515 | indexInNode: 0 // always zero for inner-portions 516 | }); 517 | } 518 | 519 | if (!startPortion && curNode.length + atIndex > match.startIndex) { 520 | // We've found the match start 521 | startPortion = { 522 | node: curNode, 523 | index: portionIndex++, 524 | indexInMatch: 0, 525 | indexInNode: match.startIndex - atIndex, 526 | endIndexInNode: match.endIndex - atIndex, 527 | text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex) 528 | }; 529 | } 530 | 531 | atIndex += curNode.data.length; 532 | 533 | } 534 | 535 | doAvoidNode = curNode.nodeType === Node.ELEMENT_NODE && elementFilter && !elementFilter(curNode); 536 | 537 | if (startPortion && endPortion) { 538 | 539 | curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion); 540 | 541 | // processMatches has to return the node that replaced the endNode 542 | // and then we step back so we can continue from the end of the 543 | // match: 544 | 545 | atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode); 546 | 547 | startPortion = null; 548 | endPortion = null; 549 | innerPortions = []; 550 | match = matches.shift(); 551 | portionIndex = 0; 552 | matchIndex++; 553 | 554 | if (!match) { 555 | break; // no more matches 556 | } 557 | 558 | } else if ( 559 | !doAvoidNode && 560 | (curNode.firstChild || curNode.nextSibling) 561 | ) { 562 | // Move down or forward: 563 | if (curNode.firstChild) { 564 | nodeStack.push(curNode); 565 | curNode = curNode.firstChild; 566 | } else { 567 | curNode = curNode.nextSibling; 568 | } 569 | continue; 570 | } 571 | 572 | // Move forward or up: 573 | while (true) { 574 | if (curNode.nextSibling) { 575 | curNode = curNode.nextSibling; 576 | break; 577 | } 578 | curNode = nodeStack.pop(); 579 | if (curNode === node) { 580 | break out; 581 | } 582 | } 583 | 584 | } 585 | 586 | }, 587 | 588 | /** 589 | * Reverts ... TODO 590 | */ 591 | revert: function() { 592 | // Reversion occurs backwards so as to avoid nodes subsequently 593 | // replaced during the matching phase (a forward process): 594 | for (var l = this.reverts.length; l--;) { 595 | this.reverts[l](); 596 | } 597 | this.reverts = []; 598 | }, 599 | 600 | prepareReplacementString: function(string, portion, match) { 601 | var portionMode = this.options.portionMode; 602 | if ( 603 | portionMode === PORTION_MODE_FIRST && 604 | portion.indexInMatch > 0 605 | ) { 606 | return ''; 607 | } 608 | string = string.replace(/\$(\d+|&|`|')/g, function($0, t) { 609 | var replacement; 610 | switch(t) { 611 | case '&': 612 | replacement = match[0]; 613 | break; 614 | case '`': 615 | replacement = match.input.substring(0, match.startIndex); 616 | break; 617 | case '\'': 618 | replacement = match.input.substring(match.endIndex); 619 | break; 620 | default: 621 | replacement = match[+t] || ''; 622 | } 623 | return replacement; 624 | }); 625 | 626 | if (portionMode === PORTION_MODE_FIRST) { 627 | return string; 628 | } 629 | 630 | if (portion.isEnd) { 631 | return string.substring(portion.indexInMatch); 632 | } 633 | 634 | return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length); 635 | }, 636 | 637 | getPortionReplacementNode: function(portion, match) { 638 | 639 | var replacement = this.options.replace || '$&'; 640 | var wrapper = this.options.wrap; 641 | var wrapperClass = this.options.wrapClass; 642 | 643 | if (wrapper && wrapper.nodeType) { 644 | // Wrapper has been provided as a stencil-node for us to clone: 645 | var clone = doc.createElement('div'); 646 | clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper); 647 | wrapper = clone.firstChild; 648 | } 649 | 650 | if (typeof replacement == 'function') { 651 | replacement = replacement(portion, match); 652 | if (replacement && replacement.nodeType) { 653 | return replacement; 654 | } 655 | return doc.createTextNode(String(replacement)); 656 | } 657 | 658 | var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper; 659 | 660 | if (el && wrapperClass) { 661 | el.className = wrapperClass; 662 | } 663 | 664 | replacement = doc.createTextNode( 665 | this.prepareReplacementString( 666 | replacement, portion, match 667 | ) 668 | ); 669 | 670 | if (!replacement.data) { 671 | return replacement; 672 | } 673 | 674 | if (!el) { 675 | return replacement; 676 | } 677 | 678 | el.appendChild(replacement); 679 | 680 | return el; 681 | }, 682 | 683 | replaceMatch: function(match, startPortion, innerPortions, endPortion) { 684 | 685 | var matchStartNode = startPortion.node; 686 | var matchEndNode = endPortion.node; 687 | 688 | var precedingTextNode; 689 | var followingTextNode; 690 | 691 | if (matchStartNode === matchEndNode) { 692 | 693 | var node = matchStartNode; 694 | 695 | if (startPortion.indexInNode > 0) { 696 | // Add `before` text node (before the match) 697 | precedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode)); 698 | node.parentNode.insertBefore(precedingTextNode, node); 699 | } 700 | 701 | // Create the replacement node: 702 | var newNode = this.getPortionReplacementNode( 703 | endPortion, 704 | match 705 | ); 706 | 707 | node.parentNode.insertBefore(newNode, node); 708 | 709 | if (endPortion.endIndexInNode < node.length) { // ????? 710 | // Add `after` text node (after the match) 711 | followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode)); 712 | node.parentNode.insertBefore(followingTextNode, node); 713 | } 714 | 715 | node.parentNode.removeChild(node); 716 | 717 | this.reverts.push(function() { 718 | if (precedingTextNode === newNode.previousSibling) { 719 | precedingTextNode.parentNode.removeChild(precedingTextNode); 720 | } 721 | if (followingTextNode === newNode.nextSibling) { 722 | followingTextNode.parentNode.removeChild(followingTextNode); 723 | } 724 | newNode.parentNode.replaceChild(node, newNode); 725 | }); 726 | 727 | return newNode; 728 | 729 | } else { 730 | // Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order) 731 | 732 | 733 | precedingTextNode = doc.createTextNode( 734 | matchStartNode.data.substring(0, startPortion.indexInNode) 735 | ); 736 | 737 | followingTextNode = doc.createTextNode( 738 | matchEndNode.data.substring(endPortion.endIndexInNode) 739 | ); 740 | 741 | var firstNode = this.getPortionReplacementNode( 742 | startPortion, 743 | match 744 | ); 745 | 746 | var innerNodes = []; 747 | 748 | for (var i = 0, l = innerPortions.length; i < l; ++i) { 749 | var portion = innerPortions[i]; 750 | var innerNode = this.getPortionReplacementNode( 751 | portion, 752 | match 753 | ); 754 | portion.node.parentNode.replaceChild(innerNode, portion.node); 755 | this.reverts.push((function(portion, innerNode) { 756 | return function() { 757 | innerNode.parentNode.replaceChild(portion.node, innerNode); 758 | }; 759 | }(portion, innerNode))); 760 | innerNodes.push(innerNode); 761 | } 762 | 763 | var lastNode = this.getPortionReplacementNode( 764 | endPortion, 765 | match 766 | ); 767 | 768 | matchStartNode.parentNode.insertBefore(precedingTextNode, matchStartNode); 769 | matchStartNode.parentNode.insertBefore(firstNode, matchStartNode); 770 | matchStartNode.parentNode.removeChild(matchStartNode); 771 | 772 | matchEndNode.parentNode.insertBefore(lastNode, matchEndNode); 773 | matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode); 774 | matchEndNode.parentNode.removeChild(matchEndNode); 775 | 776 | this.reverts.push(function() { 777 | precedingTextNode.parentNode.removeChild(precedingTextNode); 778 | firstNode.parentNode.replaceChild(matchStartNode, firstNode); 779 | followingTextNode.parentNode.removeChild(followingTextNode); 780 | lastNode.parentNode.replaceChild(matchEndNode, lastNode); 781 | }); 782 | 783 | return lastNode; 784 | } 785 | } 786 | 787 | }; 788 | 789 | return exposed; 790 | 791 | })); 792 | -------------------------------------------------------------------------------- /src/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | 7 | if (typeof jQuery === 'undefined') { 8 | throw new Error('Bootstrap\'s JavaScript requires jQuery') 9 | } 10 | 11 | +function ($) { 12 | 'use strict'; 13 | var version = $.fn.jquery.split(' ')[0].split('.') 14 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { 15 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') 16 | } 17 | }(jQuery); 18 | 19 | /* ======================================================================== 20 | * Bootstrap: transition.js v3.3.7 21 | * http://getbootstrap.com/javascript/#transitions 22 | * ======================================================================== 23 | * Copyright 2011-2016 Twitter, Inc. 24 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 25 | * ======================================================================== */ 26 | 27 | 28 | +function ($) { 29 | 'use strict'; 30 | 31 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 32 | // ============================================================ 33 | 34 | function transitionEnd() { 35 | var el = document.createElement('bootstrap') 36 | 37 | var transEndEventNames = { 38 | WebkitTransition : 'webkitTransitionEnd', 39 | MozTransition : 'transitionend', 40 | OTransition : 'oTransitionEnd otransitionend', 41 | transition : 'transitionend' 42 | } 43 | 44 | for (var name in transEndEventNames) { 45 | if (el.style[name] !== undefined) { 46 | return { end: transEndEventNames[name] } 47 | } 48 | } 49 | 50 | return false // explicit for ie8 ( ._.) 51 | } 52 | 53 | // http://blog.alexmaccaw.com/css-transitions 54 | $.fn.emulateTransitionEnd = function (duration) { 55 | var called = false 56 | var $el = this 57 | $(this).one('bsTransitionEnd', function () { called = true }) 58 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 59 | setTimeout(callback, duration) 60 | return this 61 | } 62 | 63 | $(function () { 64 | $.support.transition = transitionEnd() 65 | 66 | if (!$.support.transition) return 67 | 68 | $.event.special.bsTransitionEnd = { 69 | bindType: $.support.transition.end, 70 | delegateType: $.support.transition.end, 71 | handle: function (e) { 72 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 73 | } 74 | } 75 | }) 76 | 77 | }(jQuery); 78 | 79 | /* ======================================================================== 80 | * Bootstrap: alert.js v3.3.7 81 | * http://getbootstrap.com/javascript/#alerts 82 | * ======================================================================== 83 | * Copyright 2011-2016 Twitter, Inc. 84 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 85 | * ======================================================================== */ 86 | 87 | 88 | +function ($) { 89 | 'use strict'; 90 | 91 | // ALERT CLASS DEFINITION 92 | // ====================== 93 | 94 | var dismiss = '[data-dismiss="alert"]' 95 | var Alert = function (el) { 96 | $(el).on('click', dismiss, this.close) 97 | } 98 | 99 | Alert.VERSION = '3.3.7' 100 | 101 | Alert.TRANSITION_DURATION = 150 102 | 103 | Alert.prototype.close = function (e) { 104 | var $this = $(this) 105 | var selector = $this.attr('data-target') 106 | 107 | if (!selector) { 108 | selector = $this.attr('href') 109 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 110 | } 111 | 112 | var $parent = $(selector === '#' ? [] : selector) 113 | 114 | if (e) e.preventDefault() 115 | 116 | if (!$parent.length) { 117 | $parent = $this.closest('.alert') 118 | } 119 | 120 | $parent.trigger(e = $.Event('close.bs.alert')) 121 | 122 | if (e.isDefaultPrevented()) return 123 | 124 | $parent.removeClass('in') 125 | 126 | function removeElement() { 127 | // detach from parent, fire event then clean up data 128 | $parent.detach().trigger('closed.bs.alert').remove() 129 | } 130 | 131 | $.support.transition && $parent.hasClass('fade') ? 132 | $parent 133 | .one('bsTransitionEnd', removeElement) 134 | .emulateTransitionEnd(Alert.TRANSITION_DURATION) : 135 | removeElement() 136 | } 137 | 138 | 139 | // ALERT PLUGIN DEFINITION 140 | // ======================= 141 | 142 | function Plugin(option) { 143 | return this.each(function () { 144 | var $this = $(this) 145 | var data = $this.data('bs.alert') 146 | 147 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 148 | if (typeof option == 'string') data[option].call($this) 149 | }) 150 | } 151 | 152 | var old = $.fn.alert 153 | 154 | $.fn.alert = Plugin 155 | $.fn.alert.Constructor = Alert 156 | 157 | 158 | // ALERT NO CONFLICT 159 | // ================= 160 | 161 | $.fn.alert.noConflict = function () { 162 | $.fn.alert = old 163 | return this 164 | } 165 | 166 | 167 | // ALERT DATA-API 168 | // ============== 169 | 170 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 171 | 172 | }(jQuery); 173 | 174 | /* ======================================================================== 175 | * Bootstrap: button.js v3.3.7 176 | * http://getbootstrap.com/javascript/#buttons 177 | * ======================================================================== 178 | * Copyright 2011-2016 Twitter, Inc. 179 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 180 | * ======================================================================== */ 181 | 182 | 183 | +function ($) { 184 | 'use strict'; 185 | 186 | // BUTTON PUBLIC CLASS DEFINITION 187 | // ============================== 188 | 189 | var Button = function (element, options) { 190 | this.$element = $(element) 191 | this.options = $.extend({}, Button.DEFAULTS, options) 192 | this.isLoading = false 193 | } 194 | 195 | Button.VERSION = '3.3.7' 196 | 197 | Button.DEFAULTS = { 198 | loadingText: 'loading...' 199 | } 200 | 201 | Button.prototype.setState = function (state) { 202 | var d = 'disabled' 203 | var $el = this.$element 204 | var val = $el.is('input') ? 'val' : 'html' 205 | var data = $el.data() 206 | 207 | state += 'Text' 208 | 209 | if (data.resetText == null) $el.data('resetText', $el[val]()) 210 | 211 | // push to event loop to allow forms to submit 212 | setTimeout($.proxy(function () { 213 | $el[val](data[state] == null ? this.options[state] : data[state]) 214 | 215 | if (state == 'loadingText') { 216 | this.isLoading = true 217 | $el.addClass(d).attr(d, d).prop(d, true) 218 | } else if (this.isLoading) { 219 | this.isLoading = false 220 | $el.removeClass(d).removeAttr(d).prop(d, false) 221 | } 222 | }, this), 0) 223 | } 224 | 225 | Button.prototype.toggle = function () { 226 | var changed = true 227 | var $parent = this.$element.closest('[data-toggle="buttons"]') 228 | 229 | if ($parent.length) { 230 | var $input = this.$element.find('input') 231 | if ($input.prop('type') == 'radio') { 232 | if ($input.prop('checked')) changed = false 233 | $parent.find('.active').removeClass('active') 234 | this.$element.addClass('active') 235 | } else if ($input.prop('type') == 'checkbox') { 236 | if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false 237 | this.$element.toggleClass('active') 238 | } 239 | $input.prop('checked', this.$element.hasClass('active')) 240 | if (changed) $input.trigger('change') 241 | } else { 242 | this.$element.attr('aria-pressed', !this.$element.hasClass('active')) 243 | this.$element.toggleClass('active') 244 | } 245 | } 246 | 247 | 248 | // BUTTON PLUGIN DEFINITION 249 | // ======================== 250 | 251 | function Plugin(option) { 252 | return this.each(function () { 253 | var $this = $(this) 254 | var data = $this.data('bs.button') 255 | var options = typeof option == 'object' && option 256 | 257 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 258 | 259 | if (option == 'toggle') data.toggle() 260 | else if (option) data.setState(option) 261 | }) 262 | } 263 | 264 | var old = $.fn.button 265 | 266 | $.fn.button = Plugin 267 | $.fn.button.Constructor = Button 268 | 269 | 270 | // BUTTON NO CONFLICT 271 | // ================== 272 | 273 | $.fn.button.noConflict = function () { 274 | $.fn.button = old 275 | return this 276 | } 277 | 278 | 279 | // BUTTON DATA-API 280 | // =============== 281 | 282 | $(document) 283 | .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 284 | var $btn = $(e.target).closest('.btn') 285 | Plugin.call($btn, 'toggle') 286 | if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { 287 | // Prevent double click on radios, and the double selections (so cancellation) on checkboxes 288 | e.preventDefault() 289 | // The target component still receive the focus 290 | if ($btn.is('input,button')) $btn.trigger('focus') 291 | else $btn.find('input:visible,button:visible').first().trigger('focus') 292 | } 293 | }) 294 | .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { 295 | $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) 296 | }) 297 | 298 | }(jQuery); 299 | 300 | /* ======================================================================== 301 | * Bootstrap: carousel.js v3.3.7 302 | * http://getbootstrap.com/javascript/#carousel 303 | * ======================================================================== 304 | * Copyright 2011-2016 Twitter, Inc. 305 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 306 | * ======================================================================== */ 307 | 308 | 309 | +function ($) { 310 | 'use strict'; 311 | 312 | // CAROUSEL CLASS DEFINITION 313 | // ========================= 314 | 315 | var Carousel = function (element, options) { 316 | this.$element = $(element) 317 | this.$indicators = this.$element.find('.carousel-indicators') 318 | this.options = options 319 | this.paused = null 320 | this.sliding = null 321 | this.interval = null 322 | this.$active = null 323 | this.$items = null 324 | 325 | this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) 326 | 327 | this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element 328 | .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 329 | .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 330 | } 331 | 332 | Carousel.VERSION = '3.3.7' 333 | 334 | Carousel.TRANSITION_DURATION = 600 335 | 336 | Carousel.DEFAULTS = { 337 | interval: 5000, 338 | pause: 'hover', 339 | wrap: true, 340 | keyboard: true 341 | } 342 | 343 | Carousel.prototype.keydown = function (e) { 344 | if (/input|textarea/i.test(e.target.tagName)) return 345 | switch (e.which) { 346 | case 37: this.prev(); break 347 | case 39: this.next(); break 348 | default: return 349 | } 350 | 351 | e.preventDefault() 352 | } 353 | 354 | Carousel.prototype.cycle = function (e) { 355 | e || (this.paused = false) 356 | 357 | this.interval && clearInterval(this.interval) 358 | 359 | this.options.interval 360 | && !this.paused 361 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 362 | 363 | return this 364 | } 365 | 366 | Carousel.prototype.getItemIndex = function (item) { 367 | this.$items = item.parent().children('.item') 368 | return this.$items.index(item || this.$active) 369 | } 370 | 371 | Carousel.prototype.getItemForDirection = function (direction, active) { 372 | var activeIndex = this.getItemIndex(active) 373 | var willWrap = (direction == 'prev' && activeIndex === 0) 374 | || (direction == 'next' && activeIndex == (this.$items.length - 1)) 375 | if (willWrap && !this.options.wrap) return active 376 | var delta = direction == 'prev' ? -1 : 1 377 | var itemIndex = (activeIndex + delta) % this.$items.length 378 | return this.$items.eq(itemIndex) 379 | } 380 | 381 | Carousel.prototype.to = function (pos) { 382 | var that = this 383 | var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 384 | 385 | if (pos > (this.$items.length - 1) || pos < 0) return 386 | 387 | if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 388 | if (activeIndex == pos) return this.pause().cycle() 389 | 390 | return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) 391 | } 392 | 393 | Carousel.prototype.pause = function (e) { 394 | e || (this.paused = true) 395 | 396 | if (this.$element.find('.next, .prev').length && $.support.transition) { 397 | this.$element.trigger($.support.transition.end) 398 | this.cycle(true) 399 | } 400 | 401 | this.interval = clearInterval(this.interval) 402 | 403 | return this 404 | } 405 | 406 | Carousel.prototype.next = function () { 407 | if (this.sliding) return 408 | return this.slide('next') 409 | } 410 | 411 | Carousel.prototype.prev = function () { 412 | if (this.sliding) return 413 | return this.slide('prev') 414 | } 415 | 416 | Carousel.prototype.slide = function (type, next) { 417 | var $active = this.$element.find('.item.active') 418 | var $next = next || this.getItemForDirection(type, $active) 419 | var isCycling = this.interval 420 | var direction = type == 'next' ? 'left' : 'right' 421 | var that = this 422 | 423 | if ($next.hasClass('active')) return (this.sliding = false) 424 | 425 | var relatedTarget = $next[0] 426 | var slideEvent = $.Event('slide.bs.carousel', { 427 | relatedTarget: relatedTarget, 428 | direction: direction 429 | }) 430 | this.$element.trigger(slideEvent) 431 | if (slideEvent.isDefaultPrevented()) return 432 | 433 | this.sliding = true 434 | 435 | isCycling && this.pause() 436 | 437 | if (this.$indicators.length) { 438 | this.$indicators.find('.active').removeClass('active') 439 | var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 440 | $nextIndicator && $nextIndicator.addClass('active') 441 | } 442 | 443 | var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" 444 | if ($.support.transition && this.$element.hasClass('slide')) { 445 | $next.addClass(type) 446 | $next[0].offsetWidth // force reflow 447 | $active.addClass(direction) 448 | $next.addClass(direction) 449 | $active 450 | .one('bsTransitionEnd', function () { 451 | $next.removeClass([type, direction].join(' ')).addClass('active') 452 | $active.removeClass(['active', direction].join(' ')) 453 | that.sliding = false 454 | setTimeout(function () { 455 | that.$element.trigger(slidEvent) 456 | }, 0) 457 | }) 458 | .emulateTransitionEnd(Carousel.TRANSITION_DURATION) 459 | } else { 460 | $active.removeClass('active') 461 | $next.addClass('active') 462 | this.sliding = false 463 | this.$element.trigger(slidEvent) 464 | } 465 | 466 | isCycling && this.cycle() 467 | 468 | return this 469 | } 470 | 471 | 472 | // CAROUSEL PLUGIN DEFINITION 473 | // ========================== 474 | 475 | function Plugin(option) { 476 | return this.each(function () { 477 | var $this = $(this) 478 | var data = $this.data('bs.carousel') 479 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 480 | var action = typeof option == 'string' ? option : options.slide 481 | 482 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 483 | if (typeof option == 'number') data.to(option) 484 | else if (action) data[action]() 485 | else if (options.interval) data.pause().cycle() 486 | }) 487 | } 488 | 489 | var old = $.fn.carousel 490 | 491 | $.fn.carousel = Plugin 492 | $.fn.carousel.Constructor = Carousel 493 | 494 | 495 | // CAROUSEL NO CONFLICT 496 | // ==================== 497 | 498 | $.fn.carousel.noConflict = function () { 499 | $.fn.carousel = old 500 | return this 501 | } 502 | 503 | 504 | // CAROUSEL DATA-API 505 | // ================= 506 | 507 | var clickHandler = function (e) { 508 | var href 509 | var $this = $(this) 510 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 511 | if (!$target.hasClass('carousel')) return 512 | var options = $.extend({}, $target.data(), $this.data()) 513 | var slideIndex = $this.attr('data-slide-to') 514 | if (slideIndex) options.interval = false 515 | 516 | Plugin.call($target, options) 517 | 518 | if (slideIndex) { 519 | $target.data('bs.carousel').to(slideIndex) 520 | } 521 | 522 | e.preventDefault() 523 | } 524 | 525 | $(document) 526 | .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) 527 | .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) 528 | 529 | $(window).on('load', function () { 530 | $('[data-ride="carousel"]').each(function () { 531 | var $carousel = $(this) 532 | Plugin.call($carousel, $carousel.data()) 533 | }) 534 | }) 535 | 536 | }(jQuery); 537 | 538 | /* ======================================================================== 539 | * Bootstrap: collapse.js v3.3.7 540 | * http://getbootstrap.com/javascript/#collapse 541 | * ======================================================================== 542 | * Copyright 2011-2016 Twitter, Inc. 543 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 544 | * ======================================================================== */ 545 | 546 | /* jshint latedef: false */ 547 | 548 | +function ($) { 549 | 'use strict'; 550 | 551 | // COLLAPSE PUBLIC CLASS DEFINITION 552 | // ================================ 553 | 554 | var Collapse = function (element, options) { 555 | this.$element = $(element) 556 | this.options = $.extend({}, Collapse.DEFAULTS, options) 557 | this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + 558 | '[data-toggle="collapse"][data-target="#' + element.id + '"]') 559 | this.transitioning = null 560 | 561 | if (this.options.parent) { 562 | this.$parent = this.getParent() 563 | } else { 564 | this.addAriaAndCollapsedClass(this.$element, this.$trigger) 565 | } 566 | 567 | if (this.options.toggle) this.toggle() 568 | } 569 | 570 | Collapse.VERSION = '3.3.7' 571 | 572 | Collapse.TRANSITION_DURATION = 350 573 | 574 | Collapse.DEFAULTS = { 575 | toggle: true 576 | } 577 | 578 | Collapse.prototype.dimension = function () { 579 | var hasWidth = this.$element.hasClass('width') 580 | return hasWidth ? 'width' : 'height' 581 | } 582 | 583 | Collapse.prototype.show = function () { 584 | if (this.transitioning || this.$element.hasClass('in')) return 585 | 586 | var activesData 587 | var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') 588 | 589 | if (actives && actives.length) { 590 | activesData = actives.data('bs.collapse') 591 | if (activesData && activesData.transitioning) return 592 | } 593 | 594 | var startEvent = $.Event('show.bs.collapse') 595 | this.$element.trigger(startEvent) 596 | if (startEvent.isDefaultPrevented()) return 597 | 598 | if (actives && actives.length) { 599 | Plugin.call(actives, 'hide') 600 | activesData || actives.data('bs.collapse', null) 601 | } 602 | 603 | var dimension = this.dimension() 604 | 605 | this.$element 606 | .removeClass('collapse') 607 | .addClass('collapsing')[dimension](0) 608 | .attr('aria-expanded', true) 609 | 610 | this.$trigger 611 | .removeClass('collapsed') 612 | .attr('aria-expanded', true) 613 | 614 | this.transitioning = 1 615 | 616 | var complete = function () { 617 | this.$element 618 | .removeClass('collapsing') 619 | .addClass('collapse in')[dimension]('') 620 | this.transitioning = 0 621 | this.$element 622 | .trigger('shown.bs.collapse') 623 | } 624 | 625 | if (!$.support.transition) return complete.call(this) 626 | 627 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 628 | 629 | this.$element 630 | .one('bsTransitionEnd', $.proxy(complete, this)) 631 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) 632 | } 633 | 634 | Collapse.prototype.hide = function () { 635 | if (this.transitioning || !this.$element.hasClass('in')) return 636 | 637 | var startEvent = $.Event('hide.bs.collapse') 638 | this.$element.trigger(startEvent) 639 | if (startEvent.isDefaultPrevented()) return 640 | 641 | var dimension = this.dimension() 642 | 643 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight 644 | 645 | this.$element 646 | .addClass('collapsing') 647 | .removeClass('collapse in') 648 | .attr('aria-expanded', false) 649 | 650 | this.$trigger 651 | .addClass('collapsed') 652 | .attr('aria-expanded', false) 653 | 654 | this.transitioning = 1 655 | 656 | var complete = function () { 657 | this.transitioning = 0 658 | this.$element 659 | .removeClass('collapsing') 660 | .addClass('collapse') 661 | .trigger('hidden.bs.collapse') 662 | } 663 | 664 | if (!$.support.transition) return complete.call(this) 665 | 666 | this.$element 667 | [dimension](0) 668 | .one('bsTransitionEnd', $.proxy(complete, this)) 669 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION) 670 | } 671 | 672 | Collapse.prototype.toggle = function () { 673 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 674 | } 675 | 676 | Collapse.prototype.getParent = function () { 677 | return $(this.options.parent) 678 | .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') 679 | .each($.proxy(function (i, element) { 680 | var $element = $(element) 681 | this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) 682 | }, this)) 683 | .end() 684 | } 685 | 686 | Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { 687 | var isOpen = $element.hasClass('in') 688 | 689 | $element.attr('aria-expanded', isOpen) 690 | $trigger 691 | .toggleClass('collapsed', !isOpen) 692 | .attr('aria-expanded', isOpen) 693 | } 694 | 695 | function getTargetFromTrigger($trigger) { 696 | var href 697 | var target = $trigger.attr('data-target') 698 | || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 699 | 700 | return $(target) 701 | } 702 | 703 | 704 | // COLLAPSE PLUGIN DEFINITION 705 | // ========================== 706 | 707 | function Plugin(option) { 708 | return this.each(function () { 709 | var $this = $(this) 710 | var data = $this.data('bs.collapse') 711 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 712 | 713 | if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false 714 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 715 | if (typeof option == 'string') data[option]() 716 | }) 717 | } 718 | 719 | var old = $.fn.collapse 720 | 721 | $.fn.collapse = Plugin 722 | $.fn.collapse.Constructor = Collapse 723 | 724 | 725 | // COLLAPSE NO CONFLICT 726 | // ==================== 727 | 728 | $.fn.collapse.noConflict = function () { 729 | $.fn.collapse = old 730 | return this 731 | } 732 | 733 | 734 | // COLLAPSE DATA-API 735 | // ================= 736 | 737 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 738 | var $this = $(this) 739 | 740 | if (!$this.attr('data-target')) e.preventDefault() 741 | 742 | var $target = getTargetFromTrigger($this) 743 | var data = $target.data('bs.collapse') 744 | var option = data ? 'toggle' : $this.data() 745 | 746 | Plugin.call($target, option) 747 | }) 748 | 749 | }(jQuery); 750 | 751 | /* ======================================================================== 752 | * Bootstrap: dropdown.js v3.3.7 753 | * http://getbootstrap.com/javascript/#dropdowns 754 | * ======================================================================== 755 | * Copyright 2011-2016 Twitter, Inc. 756 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 757 | * ======================================================================== */ 758 | 759 | 760 | +function ($) { 761 | 'use strict'; 762 | 763 | // DROPDOWN CLASS DEFINITION 764 | // ========================= 765 | 766 | var backdrop = '.dropdown-backdrop' 767 | var toggle = '[data-toggle="dropdown"]' 768 | var Dropdown = function (element) { 769 | $(element).on('click.bs.dropdown', this.toggle) 770 | } 771 | 772 | Dropdown.VERSION = '3.3.7' 773 | 774 | function getParent($this) { 775 | var selector = $this.attr('data-target') 776 | 777 | if (!selector) { 778 | selector = $this.attr('href') 779 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 780 | } 781 | 782 | var $parent = selector && $(selector) 783 | 784 | return $parent && $parent.length ? $parent : $this.parent() 785 | } 786 | 787 | function clearMenus(e) { 788 | if (e && e.which === 3) return 789 | $(backdrop).remove() 790 | $(toggle).each(function () { 791 | var $this = $(this) 792 | var $parent = getParent($this) 793 | var relatedTarget = { relatedTarget: this } 794 | 795 | if (!$parent.hasClass('open')) return 796 | 797 | if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return 798 | 799 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 800 | 801 | if (e.isDefaultPrevented()) return 802 | 803 | $this.attr('aria-expanded', 'false') 804 | $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) 805 | }) 806 | } 807 | 808 | Dropdown.prototype.toggle = function (e) { 809 | var $this = $(this) 810 | 811 | if ($this.is('.disabled, :disabled')) return 812 | 813 | var $parent = getParent($this) 814 | var isActive = $parent.hasClass('open') 815 | 816 | clearMenus() 817 | 818 | if (!isActive) { 819 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 820 | // if mobile we use a backdrop because click events don't delegate 821 | $(document.createElement('div')) 822 | .addClass('dropdown-backdrop') 823 | .insertAfter($(this)) 824 | .on('click', clearMenus) 825 | } 826 | 827 | var relatedTarget = { relatedTarget: this } 828 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 829 | 830 | if (e.isDefaultPrevented()) return 831 | 832 | $this 833 | .trigger('focus') 834 | .attr('aria-expanded', 'true') 835 | 836 | $parent 837 | .toggleClass('open') 838 | .trigger($.Event('shown.bs.dropdown', relatedTarget)) 839 | } 840 | 841 | return false 842 | } 843 | 844 | Dropdown.prototype.keydown = function (e) { 845 | if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return 846 | 847 | var $this = $(this) 848 | 849 | e.preventDefault() 850 | e.stopPropagation() 851 | 852 | if ($this.is('.disabled, :disabled')) return 853 | 854 | var $parent = getParent($this) 855 | var isActive = $parent.hasClass('open') 856 | 857 | if (!isActive && e.which != 27 || isActive && e.which == 27) { 858 | if (e.which == 27) $parent.find(toggle).trigger('focus') 859 | return $this.trigger('click') 860 | } 861 | 862 | var desc = ' li:not(.disabled):visible a' 863 | var $items = $parent.find('.dropdown-menu' + desc) 864 | 865 | if (!$items.length) return 866 | 867 | var index = $items.index(e.target) 868 | 869 | if (e.which == 38 && index > 0) index-- // up 870 | if (e.which == 40 && index < $items.length - 1) index++ // down 871 | if (!~index) index = 0 872 | 873 | $items.eq(index).trigger('focus') 874 | } 875 | 876 | 877 | // DROPDOWN PLUGIN DEFINITION 878 | // ========================== 879 | 880 | function Plugin(option) { 881 | return this.each(function () { 882 | var $this = $(this) 883 | var data = $this.data('bs.dropdown') 884 | 885 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 886 | if (typeof option == 'string') data[option].call($this) 887 | }) 888 | } 889 | 890 | var old = $.fn.dropdown 891 | 892 | $.fn.dropdown = Plugin 893 | $.fn.dropdown.Constructor = Dropdown 894 | 895 | 896 | // DROPDOWN NO CONFLICT 897 | // ==================== 898 | 899 | $.fn.dropdown.noConflict = function () { 900 | $.fn.dropdown = old 901 | return this 902 | } 903 | 904 | 905 | // APPLY TO STANDARD DROPDOWN ELEMENTS 906 | // =================================== 907 | 908 | $(document) 909 | .on('click.bs.dropdown.data-api', clearMenus) 910 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 911 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) 912 | .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) 913 | .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) 914 | 915 | }(jQuery); 916 | 917 | /* ======================================================================== 918 | * Bootstrap: modal.js v3.3.7 919 | * http://getbootstrap.com/javascript/#modals 920 | * ======================================================================== 921 | * Copyright 2011-2016 Twitter, Inc. 922 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 923 | * ======================================================================== */ 924 | 925 | 926 | +function ($) { 927 | 'use strict'; 928 | 929 | // MODAL CLASS DEFINITION 930 | // ====================== 931 | 932 | var Modal = function (element, options) { 933 | this.options = options 934 | this.$body = $(document.body) 935 | this.$element = $(element) 936 | this.$dialog = this.$element.find('.modal-dialog') 937 | this.$backdrop = null 938 | this.isShown = null 939 | this.originalBodyPad = null 940 | this.scrollbarWidth = 0 941 | this.ignoreBackdropClick = false 942 | 943 | if (this.options.remote) { 944 | this.$element 945 | .find('.modal-content') 946 | .load(this.options.remote, $.proxy(function () { 947 | this.$element.trigger('loaded.bs.modal') 948 | }, this)) 949 | } 950 | } 951 | 952 | Modal.VERSION = '3.3.7' 953 | 954 | Modal.TRANSITION_DURATION = 300 955 | Modal.BACKDROP_TRANSITION_DURATION = 150 956 | 957 | Modal.DEFAULTS = { 958 | backdrop: true, 959 | keyboard: true, 960 | show: true 961 | } 962 | 963 | Modal.prototype.toggle = function (_relatedTarget) { 964 | return this.isShown ? this.hide() : this.show(_relatedTarget) 965 | } 966 | 967 | Modal.prototype.show = function (_relatedTarget) { 968 | var that = this 969 | var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 970 | 971 | this.$element.trigger(e) 972 | 973 | if (this.isShown || e.isDefaultPrevented()) return 974 | 975 | this.isShown = true 976 | 977 | this.checkScrollbar() 978 | this.setScrollbar() 979 | this.$body.addClass('modal-open') 980 | 981 | this.escape() 982 | this.resize() 983 | 984 | this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 985 | 986 | this.$dialog.on('mousedown.dismiss.bs.modal', function () { 987 | that.$element.one('mouseup.dismiss.bs.modal', function (e) { 988 | if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true 989 | }) 990 | }) 991 | 992 | this.backdrop(function () { 993 | var transition = $.support.transition && that.$element.hasClass('fade') 994 | 995 | if (!that.$element.parent().length) { 996 | that.$element.appendTo(that.$body) // don't move modals dom position 997 | } 998 | 999 | that.$element 1000 | .show() 1001 | .scrollTop(0) 1002 | 1003 | that.adjustDialog() 1004 | 1005 | if (transition) { 1006 | that.$element[0].offsetWidth // force reflow 1007 | } 1008 | 1009 | that.$element.addClass('in') 1010 | 1011 | that.enforceFocus() 1012 | 1013 | var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 1014 | 1015 | transition ? 1016 | that.$dialog // wait for modal to slide in 1017 | .one('bsTransitionEnd', function () { 1018 | that.$element.trigger('focus').trigger(e) 1019 | }) 1020 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1021 | that.$element.trigger('focus').trigger(e) 1022 | }) 1023 | } 1024 | 1025 | Modal.prototype.hide = function (e) { 1026 | if (e) e.preventDefault() 1027 | 1028 | e = $.Event('hide.bs.modal') 1029 | 1030 | this.$element.trigger(e) 1031 | 1032 | if (!this.isShown || e.isDefaultPrevented()) return 1033 | 1034 | this.isShown = false 1035 | 1036 | this.escape() 1037 | this.resize() 1038 | 1039 | $(document).off('focusin.bs.modal') 1040 | 1041 | this.$element 1042 | .removeClass('in') 1043 | .off('click.dismiss.bs.modal') 1044 | .off('mouseup.dismiss.bs.modal') 1045 | 1046 | this.$dialog.off('mousedown.dismiss.bs.modal') 1047 | 1048 | $.support.transition && this.$element.hasClass('fade') ? 1049 | this.$element 1050 | .one('bsTransitionEnd', $.proxy(this.hideModal, this)) 1051 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1052 | this.hideModal() 1053 | } 1054 | 1055 | Modal.prototype.enforceFocus = function () { 1056 | $(document) 1057 | .off('focusin.bs.modal') // guard against infinite focus loop 1058 | .on('focusin.bs.modal', $.proxy(function (e) { 1059 | if (document !== e.target && 1060 | this.$element[0] !== e.target && 1061 | !this.$element.has(e.target).length) { 1062 | this.$element.trigger('focus') 1063 | } 1064 | }, this)) 1065 | } 1066 | 1067 | Modal.prototype.escape = function () { 1068 | if (this.isShown && this.options.keyboard) { 1069 | this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { 1070 | e.which == 27 && this.hide() 1071 | }, this)) 1072 | } else if (!this.isShown) { 1073 | this.$element.off('keydown.dismiss.bs.modal') 1074 | } 1075 | } 1076 | 1077 | Modal.prototype.resize = function () { 1078 | if (this.isShown) { 1079 | $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) 1080 | } else { 1081 | $(window).off('resize.bs.modal') 1082 | } 1083 | } 1084 | 1085 | Modal.prototype.hideModal = function () { 1086 | var that = this 1087 | this.$element.hide() 1088 | this.backdrop(function () { 1089 | that.$body.removeClass('modal-open') 1090 | that.resetAdjustments() 1091 | that.resetScrollbar() 1092 | that.$element.trigger('hidden.bs.modal') 1093 | }) 1094 | } 1095 | 1096 | Modal.prototype.removeBackdrop = function () { 1097 | this.$backdrop && this.$backdrop.remove() 1098 | this.$backdrop = null 1099 | } 1100 | 1101 | Modal.prototype.backdrop = function (callback) { 1102 | var that = this 1103 | var animate = this.$element.hasClass('fade') ? 'fade' : '' 1104 | 1105 | if (this.isShown && this.options.backdrop) { 1106 | var doAnimate = $.support.transition && animate 1107 | 1108 | this.$backdrop = $(document.createElement('div')) 1109 | .addClass('modal-backdrop ' + animate) 1110 | .appendTo(this.$body) 1111 | 1112 | this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 1113 | if (this.ignoreBackdropClick) { 1114 | this.ignoreBackdropClick = false 1115 | return 1116 | } 1117 | if (e.target !== e.currentTarget) return 1118 | this.options.backdrop == 'static' 1119 | ? this.$element[0].focus() 1120 | : this.hide() 1121 | }, this)) 1122 | 1123 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 1124 | 1125 | this.$backdrop.addClass('in') 1126 | 1127 | if (!callback) return 1128 | 1129 | doAnimate ? 1130 | this.$backdrop 1131 | .one('bsTransitionEnd', callback) 1132 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1133 | callback() 1134 | 1135 | } else if (!this.isShown && this.$backdrop) { 1136 | this.$backdrop.removeClass('in') 1137 | 1138 | var callbackRemove = function () { 1139 | that.removeBackdrop() 1140 | callback && callback() 1141 | } 1142 | $.support.transition && this.$element.hasClass('fade') ? 1143 | this.$backdrop 1144 | .one('bsTransitionEnd', callbackRemove) 1145 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1146 | callbackRemove() 1147 | 1148 | } else if (callback) { 1149 | callback() 1150 | } 1151 | } 1152 | 1153 | // these following methods are used to handle overflowing modals 1154 | 1155 | Modal.prototype.handleUpdate = function () { 1156 | this.adjustDialog() 1157 | } 1158 | 1159 | Modal.prototype.adjustDialog = function () { 1160 | var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight 1161 | 1162 | this.$element.css({ 1163 | paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', 1164 | paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' 1165 | }) 1166 | } 1167 | 1168 | Modal.prototype.resetAdjustments = function () { 1169 | this.$element.css({ 1170 | paddingLeft: '', 1171 | paddingRight: '' 1172 | }) 1173 | } 1174 | 1175 | Modal.prototype.checkScrollbar = function () { 1176 | var fullWindowWidth = window.innerWidth 1177 | if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 1178 | var documentElementRect = document.documentElement.getBoundingClientRect() 1179 | fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) 1180 | } 1181 | this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth 1182 | this.scrollbarWidth = this.measureScrollbar() 1183 | } 1184 | 1185 | Modal.prototype.setScrollbar = function () { 1186 | var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) 1187 | this.originalBodyPad = document.body.style.paddingRight || '' 1188 | if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) 1189 | } 1190 | 1191 | Modal.prototype.resetScrollbar = function () { 1192 | this.$body.css('padding-right', this.originalBodyPad) 1193 | } 1194 | 1195 | Modal.prototype.measureScrollbar = function () { // thx walsh 1196 | var scrollDiv = document.createElement('div') 1197 | scrollDiv.className = 'modal-scrollbar-measure' 1198 | this.$body.append(scrollDiv) 1199 | var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 1200 | this.$body[0].removeChild(scrollDiv) 1201 | return scrollbarWidth 1202 | } 1203 | 1204 | 1205 | // MODAL PLUGIN DEFINITION 1206 | // ======================= 1207 | 1208 | function Plugin(option, _relatedTarget) { 1209 | return this.each(function () { 1210 | var $this = $(this) 1211 | var data = $this.data('bs.modal') 1212 | var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) 1213 | 1214 | if (!data) $this.data('bs.modal', (data = new Modal(this, options))) 1215 | if (typeof option == 'string') data[option](_relatedTarget) 1216 | else if (options.show) data.show(_relatedTarget) 1217 | }) 1218 | } 1219 | 1220 | var old = $.fn.modal 1221 | 1222 | $.fn.modal = Plugin 1223 | $.fn.modal.Constructor = Modal 1224 | 1225 | 1226 | // MODAL NO CONFLICT 1227 | // ================= 1228 | 1229 | $.fn.modal.noConflict = function () { 1230 | $.fn.modal = old 1231 | return this 1232 | } 1233 | 1234 | 1235 | // MODAL DATA-API 1236 | // ============== 1237 | 1238 | $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { 1239 | var $this = $(this) 1240 | var href = $this.attr('href') 1241 | var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 1242 | var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) 1243 | 1244 | if ($this.is('a')) e.preventDefault() 1245 | 1246 | $target.one('show.bs.modal', function (showEvent) { 1247 | if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown 1248 | $target.one('hidden.bs.modal', function () { 1249 | $this.is(':visible') && $this.trigger('focus') 1250 | }) 1251 | }) 1252 | Plugin.call($target, option, this) 1253 | }) 1254 | 1255 | }(jQuery); 1256 | 1257 | /* ======================================================================== 1258 | * Bootstrap: tooltip.js v3.3.7 1259 | * http://getbootstrap.com/javascript/#tooltip 1260 | * Inspired by the original jQuery.tipsy by Jason Frame 1261 | * ======================================================================== 1262 | * Copyright 2011-2016 Twitter, Inc. 1263 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1264 | * ======================================================================== */ 1265 | 1266 | 1267 | +function ($) { 1268 | 'use strict'; 1269 | 1270 | // TOOLTIP PUBLIC CLASS DEFINITION 1271 | // =============================== 1272 | 1273 | var Tooltip = function (element, options) { 1274 | this.type = null 1275 | this.options = null 1276 | this.enabled = null 1277 | this.timeout = null 1278 | this.hoverState = null 1279 | this.$element = null 1280 | this.inState = null 1281 | 1282 | this.init('tooltip', element, options) 1283 | } 1284 | 1285 | Tooltip.VERSION = '3.3.7' 1286 | 1287 | Tooltip.TRANSITION_DURATION = 150 1288 | 1289 | Tooltip.DEFAULTS = { 1290 | animation: true, 1291 | placement: 'top', 1292 | selector: false, 1293 | template: '', 1294 | trigger: 'hover focus', 1295 | title: '', 1296 | delay: 0, 1297 | html: false, 1298 | container: false, 1299 | viewport: { 1300 | selector: 'body', 1301 | padding: 0 1302 | } 1303 | } 1304 | 1305 | Tooltip.prototype.init = function (type, element, options) { 1306 | this.enabled = true 1307 | this.type = type 1308 | this.$element = $(element) 1309 | this.options = this.getOptions(options) 1310 | this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) 1311 | this.inState = { click: false, hover: false, focus: false } 1312 | 1313 | if (this.$element[0] instanceof document.constructor && !this.options.selector) { 1314 | throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') 1315 | } 1316 | 1317 | var triggers = this.options.trigger.split(' ') 1318 | 1319 | for (var i = triggers.length; i--;) { 1320 | var trigger = triggers[i] 1321 | 1322 | if (trigger == 'click') { 1323 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) 1324 | } else if (trigger != 'manual') { 1325 | var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' 1326 | var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' 1327 | 1328 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) 1329 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) 1330 | } 1331 | } 1332 | 1333 | this.options.selector ? 1334 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 1335 | this.fixTitle() 1336 | } 1337 | 1338 | Tooltip.prototype.getDefaults = function () { 1339 | return Tooltip.DEFAULTS 1340 | } 1341 | 1342 | Tooltip.prototype.getOptions = function (options) { 1343 | options = $.extend({}, this.getDefaults(), this.$element.data(), options) 1344 | 1345 | if (options.delay && typeof options.delay == 'number') { 1346 | options.delay = { 1347 | show: options.delay, 1348 | hide: options.delay 1349 | } 1350 | } 1351 | 1352 | return options 1353 | } 1354 | 1355 | Tooltip.prototype.getDelegateOptions = function () { 1356 | var options = {} 1357 | var defaults = this.getDefaults() 1358 | 1359 | this._options && $.each(this._options, function (key, value) { 1360 | if (defaults[key] != value) options[key] = value 1361 | }) 1362 | 1363 | return options 1364 | } 1365 | 1366 | Tooltip.prototype.enter = function (obj) { 1367 | var self = obj instanceof this.constructor ? 1368 | obj : $(obj.currentTarget).data('bs.' + this.type) 1369 | 1370 | if (!self) { 1371 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1372 | $(obj.currentTarget).data('bs.' + this.type, self) 1373 | } 1374 | 1375 | if (obj instanceof $.Event) { 1376 | self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true 1377 | } 1378 | 1379 | if (self.tip().hasClass('in') || self.hoverState == 'in') { 1380 | self.hoverState = 'in' 1381 | return 1382 | } 1383 | 1384 | clearTimeout(self.timeout) 1385 | 1386 | self.hoverState = 'in' 1387 | 1388 | if (!self.options.delay || !self.options.delay.show) return self.show() 1389 | 1390 | self.timeout = setTimeout(function () { 1391 | if (self.hoverState == 'in') self.show() 1392 | }, self.options.delay.show) 1393 | } 1394 | 1395 | Tooltip.prototype.isInStateTrue = function () { 1396 | for (var key in this.inState) { 1397 | if (this.inState[key]) return true 1398 | } 1399 | 1400 | return false 1401 | } 1402 | 1403 | Tooltip.prototype.leave = function (obj) { 1404 | var self = obj instanceof this.constructor ? 1405 | obj : $(obj.currentTarget).data('bs.' + this.type) 1406 | 1407 | if (!self) { 1408 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1409 | $(obj.currentTarget).data('bs.' + this.type, self) 1410 | } 1411 | 1412 | if (obj instanceof $.Event) { 1413 | self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false 1414 | } 1415 | 1416 | if (self.isInStateTrue()) return 1417 | 1418 | clearTimeout(self.timeout) 1419 | 1420 | self.hoverState = 'out' 1421 | 1422 | if (!self.options.delay || !self.options.delay.hide) return self.hide() 1423 | 1424 | self.timeout = setTimeout(function () { 1425 | if (self.hoverState == 'out') self.hide() 1426 | }, self.options.delay.hide) 1427 | } 1428 | 1429 | Tooltip.prototype.show = function () { 1430 | var e = $.Event('show.bs.' + this.type) 1431 | 1432 | if (this.hasContent() && this.enabled) { 1433 | this.$element.trigger(e) 1434 | 1435 | var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) 1436 | if (e.isDefaultPrevented() || !inDom) return 1437 | var that = this 1438 | 1439 | var $tip = this.tip() 1440 | 1441 | var tipId = this.getUID(this.type) 1442 | 1443 | this.setContent() 1444 | $tip.attr('id', tipId) 1445 | this.$element.attr('aria-describedby', tipId) 1446 | 1447 | if (this.options.animation) $tip.addClass('fade') 1448 | 1449 | var placement = typeof this.options.placement == 'function' ? 1450 | this.options.placement.call(this, $tip[0], this.$element[0]) : 1451 | this.options.placement 1452 | 1453 | var autoToken = /\s?auto?\s?/i 1454 | var autoPlace = autoToken.test(placement) 1455 | if (autoPlace) placement = placement.replace(autoToken, '') || 'top' 1456 | 1457 | $tip 1458 | .detach() 1459 | .css({ top: 0, left: 0, display: 'block' }) 1460 | .addClass(placement) 1461 | .data('bs.' + this.type, this) 1462 | 1463 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) 1464 | this.$element.trigger('inserted.bs.' + this.type) 1465 | 1466 | var pos = this.getPosition() 1467 | var actualWidth = $tip[0].offsetWidth 1468 | var actualHeight = $tip[0].offsetHeight 1469 | 1470 | if (autoPlace) { 1471 | var orgPlacement = placement 1472 | var viewportDim = this.getPosition(this.$viewport) 1473 | 1474 | placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : 1475 | placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : 1476 | placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : 1477 | placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : 1478 | placement 1479 | 1480 | $tip 1481 | .removeClass(orgPlacement) 1482 | .addClass(placement) 1483 | } 1484 | 1485 | var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) 1486 | 1487 | this.applyPlacement(calculatedOffset, placement) 1488 | 1489 | var complete = function () { 1490 | var prevHoverState = that.hoverState 1491 | that.$element.trigger('shown.bs.' + that.type) 1492 | that.hoverState = null 1493 | 1494 | if (prevHoverState == 'out') that.leave(that) 1495 | } 1496 | 1497 | $.support.transition && this.$tip.hasClass('fade') ? 1498 | $tip 1499 | .one('bsTransitionEnd', complete) 1500 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1501 | complete() 1502 | } 1503 | } 1504 | 1505 | Tooltip.prototype.applyPlacement = function (offset, placement) { 1506 | var $tip = this.tip() 1507 | var width = $tip[0].offsetWidth 1508 | var height = $tip[0].offsetHeight 1509 | 1510 | // manually read margins because getBoundingClientRect includes difference 1511 | var marginTop = parseInt($tip.css('margin-top'), 10) 1512 | var marginLeft = parseInt($tip.css('margin-left'), 10) 1513 | 1514 | // we must check for NaN for ie 8/9 1515 | if (isNaN(marginTop)) marginTop = 0 1516 | if (isNaN(marginLeft)) marginLeft = 0 1517 | 1518 | offset.top += marginTop 1519 | offset.left += marginLeft 1520 | 1521 | // $.fn.offset doesn't round pixel values 1522 | // so we use setOffset directly with our own function B-0 1523 | $.offset.setOffset($tip[0], $.extend({ 1524 | using: function (props) { 1525 | $tip.css({ 1526 | top: Math.round(props.top), 1527 | left: Math.round(props.left) 1528 | }) 1529 | } 1530 | }, offset), 0) 1531 | 1532 | $tip.addClass('in') 1533 | 1534 | // check to see if placing tip in new offset caused the tip to resize itself 1535 | var actualWidth = $tip[0].offsetWidth 1536 | var actualHeight = $tip[0].offsetHeight 1537 | 1538 | if (placement == 'top' && actualHeight != height) { 1539 | offset.top = offset.top + height - actualHeight 1540 | } 1541 | 1542 | var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) 1543 | 1544 | if (delta.left) offset.left += delta.left 1545 | else offset.top += delta.top 1546 | 1547 | var isVertical = /top|bottom/.test(placement) 1548 | var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight 1549 | var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' 1550 | 1551 | $tip.offset(offset) 1552 | this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) 1553 | } 1554 | 1555 | Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { 1556 | this.arrow() 1557 | .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') 1558 | .css(isVertical ? 'top' : 'left', '') 1559 | } 1560 | 1561 | Tooltip.prototype.setContent = function () { 1562 | var $tip = this.tip() 1563 | var title = this.getTitle() 1564 | 1565 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) 1566 | $tip.removeClass('fade in top bottom left right') 1567 | } 1568 | 1569 | Tooltip.prototype.hide = function (callback) { 1570 | var that = this 1571 | var $tip = $(this.$tip) 1572 | var e = $.Event('hide.bs.' + this.type) 1573 | 1574 | function complete() { 1575 | if (that.hoverState != 'in') $tip.detach() 1576 | if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. 1577 | that.$element 1578 | .removeAttr('aria-describedby') 1579 | .trigger('hidden.bs.' + that.type) 1580 | } 1581 | callback && callback() 1582 | } 1583 | 1584 | this.$element.trigger(e) 1585 | 1586 | if (e.isDefaultPrevented()) return 1587 | 1588 | $tip.removeClass('in') 1589 | 1590 | $.support.transition && $tip.hasClass('fade') ? 1591 | $tip 1592 | .one('bsTransitionEnd', complete) 1593 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1594 | complete() 1595 | 1596 | this.hoverState = null 1597 | 1598 | return this 1599 | } 1600 | 1601 | Tooltip.prototype.fixTitle = function () { 1602 | var $e = this.$element 1603 | if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { 1604 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') 1605 | } 1606 | } 1607 | 1608 | Tooltip.prototype.hasContent = function () { 1609 | return this.getTitle() 1610 | } 1611 | 1612 | Tooltip.prototype.getPosition = function ($element) { 1613 | $element = $element || this.$element 1614 | 1615 | var el = $element[0] 1616 | var isBody = el.tagName == 'BODY' 1617 | 1618 | var elRect = el.getBoundingClientRect() 1619 | if (elRect.width == null) { 1620 | // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 1621 | elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) 1622 | } 1623 | var isSvg = window.SVGElement && el instanceof window.SVGElement 1624 | // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. 1625 | // See https://github.com/twbs/bootstrap/issues/20280 1626 | var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) 1627 | var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } 1628 | var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null 1629 | 1630 | return $.extend({}, elRect, scroll, outerDims, elOffset) 1631 | } 1632 | 1633 | Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { 1634 | return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1635 | placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1636 | placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : 1637 | /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } 1638 | 1639 | } 1640 | 1641 | Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { 1642 | var delta = { top: 0, left: 0 } 1643 | if (!this.$viewport) return delta 1644 | 1645 | var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 1646 | var viewportDimensions = this.getPosition(this.$viewport) 1647 | 1648 | if (/right|left/.test(placement)) { 1649 | var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll 1650 | var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight 1651 | if (topEdgeOffset < viewportDimensions.top) { // top overflow 1652 | delta.top = viewportDimensions.top - topEdgeOffset 1653 | } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow 1654 | delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset 1655 | } 1656 | } else { 1657 | var leftEdgeOffset = pos.left - viewportPadding 1658 | var rightEdgeOffset = pos.left + viewportPadding + actualWidth 1659 | if (leftEdgeOffset < viewportDimensions.left) { // left overflow 1660 | delta.left = viewportDimensions.left - leftEdgeOffset 1661 | } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow 1662 | delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset 1663 | } 1664 | } 1665 | 1666 | return delta 1667 | } 1668 | 1669 | Tooltip.prototype.getTitle = function () { 1670 | var title 1671 | var $e = this.$element 1672 | var o = this.options 1673 | 1674 | title = $e.attr('data-original-title') 1675 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 1676 | 1677 | return title 1678 | } 1679 | 1680 | Tooltip.prototype.getUID = function (prefix) { 1681 | do prefix += ~~(Math.random() * 1000000) 1682 | while (document.getElementById(prefix)) 1683 | return prefix 1684 | } 1685 | 1686 | Tooltip.prototype.tip = function () { 1687 | if (!this.$tip) { 1688 | this.$tip = $(this.options.template) 1689 | if (this.$tip.length != 1) { 1690 | throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') 1691 | } 1692 | } 1693 | return this.$tip 1694 | } 1695 | 1696 | Tooltip.prototype.arrow = function () { 1697 | return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) 1698 | } 1699 | 1700 | Tooltip.prototype.enable = function () { 1701 | this.enabled = true 1702 | } 1703 | 1704 | Tooltip.prototype.disable = function () { 1705 | this.enabled = false 1706 | } 1707 | 1708 | Tooltip.prototype.toggleEnabled = function () { 1709 | this.enabled = !this.enabled 1710 | } 1711 | 1712 | Tooltip.prototype.toggle = function (e) { 1713 | var self = this 1714 | if (e) { 1715 | self = $(e.currentTarget).data('bs.' + this.type) 1716 | if (!self) { 1717 | self = new this.constructor(e.currentTarget, this.getDelegateOptions()) 1718 | $(e.currentTarget).data('bs.' + this.type, self) 1719 | } 1720 | } 1721 | 1722 | if (e) { 1723 | self.inState.click = !self.inState.click 1724 | if (self.isInStateTrue()) self.enter(self) 1725 | else self.leave(self) 1726 | } else { 1727 | self.tip().hasClass('in') ? self.leave(self) : self.enter(self) 1728 | } 1729 | } 1730 | 1731 | Tooltip.prototype.destroy = function () { 1732 | var that = this 1733 | clearTimeout(this.timeout) 1734 | this.hide(function () { 1735 | that.$element.off('.' + that.type).removeData('bs.' + that.type) 1736 | if (that.$tip) { 1737 | that.$tip.detach() 1738 | } 1739 | that.$tip = null 1740 | that.$arrow = null 1741 | that.$viewport = null 1742 | that.$element = null 1743 | }) 1744 | } 1745 | 1746 | 1747 | // TOOLTIP PLUGIN DEFINITION 1748 | // ========================= 1749 | 1750 | function Plugin(option) { 1751 | return this.each(function () { 1752 | var $this = $(this) 1753 | var data = $this.data('bs.tooltip') 1754 | var options = typeof option == 'object' && option 1755 | 1756 | if (!data && /destroy|hide/.test(option)) return 1757 | if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) 1758 | if (typeof option == 'string') data[option]() 1759 | }) 1760 | } 1761 | 1762 | var old = $.fn.tooltip 1763 | 1764 | $.fn.tooltip = Plugin 1765 | $.fn.tooltip.Constructor = Tooltip 1766 | 1767 | 1768 | // TOOLTIP NO CONFLICT 1769 | // =================== 1770 | 1771 | $.fn.tooltip.noConflict = function () { 1772 | $.fn.tooltip = old 1773 | return this 1774 | } 1775 | 1776 | }(jQuery); 1777 | 1778 | /* ======================================================================== 1779 | * Bootstrap: popover.js v3.3.7 1780 | * http://getbootstrap.com/javascript/#popovers 1781 | * ======================================================================== 1782 | * Copyright 2011-2016 Twitter, Inc. 1783 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1784 | * ======================================================================== */ 1785 | 1786 | 1787 | +function ($) { 1788 | 'use strict'; 1789 | 1790 | // POPOVER PUBLIC CLASS DEFINITION 1791 | // =============================== 1792 | 1793 | var Popover = function (element, options) { 1794 | this.init('popover', element, options) 1795 | } 1796 | 1797 | if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') 1798 | 1799 | Popover.VERSION = '3.3.7' 1800 | 1801 | Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { 1802 | placement: 'right', 1803 | trigger: 'click', 1804 | content: '', 1805 | template: '' 1806 | }) 1807 | 1808 | 1809 | // NOTE: POPOVER EXTENDS tooltip.js 1810 | // ================================ 1811 | 1812 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) 1813 | 1814 | Popover.prototype.constructor = Popover 1815 | 1816 | Popover.prototype.getDefaults = function () { 1817 | return Popover.DEFAULTS 1818 | } 1819 | 1820 | Popover.prototype.setContent = function () { 1821 | var $tip = this.tip() 1822 | var title = this.getTitle() 1823 | var content = this.getContent() 1824 | 1825 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 1826 | $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events 1827 | this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' 1828 | ](content) 1829 | 1830 | $tip.removeClass('fade top bottom left right in') 1831 | 1832 | // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do 1833 | // this manually by checking the contents. 1834 | if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() 1835 | } 1836 | 1837 | Popover.prototype.hasContent = function () { 1838 | return this.getTitle() || this.getContent() 1839 | } 1840 | 1841 | Popover.prototype.getContent = function () { 1842 | var $e = this.$element 1843 | var o = this.options 1844 | 1845 | return $e.attr('data-content') 1846 | || (typeof o.content == 'function' ? 1847 | o.content.call($e[0]) : 1848 | o.content) 1849 | } 1850 | 1851 | Popover.prototype.arrow = function () { 1852 | return (this.$arrow = this.$arrow || this.tip().find('.arrow')) 1853 | } 1854 | 1855 | 1856 | // POPOVER PLUGIN DEFINITION 1857 | // ========================= 1858 | 1859 | function Plugin(option) { 1860 | return this.each(function () { 1861 | var $this = $(this) 1862 | var data = $this.data('bs.popover') 1863 | var options = typeof option == 'object' && option 1864 | 1865 | if (!data && /destroy|hide/.test(option)) return 1866 | if (!data) $this.data('bs.popover', (data = new Popover(this, options))) 1867 | if (typeof option == 'string') data[option]() 1868 | }) 1869 | } 1870 | 1871 | var old = $.fn.popover 1872 | 1873 | $.fn.popover = Plugin 1874 | $.fn.popover.Constructor = Popover 1875 | 1876 | 1877 | // POPOVER NO CONFLICT 1878 | // =================== 1879 | 1880 | $.fn.popover.noConflict = function () { 1881 | $.fn.popover = old 1882 | return this 1883 | } 1884 | 1885 | }(jQuery); 1886 | 1887 | /* ======================================================================== 1888 | * Bootstrap: scrollspy.js v3.3.7 1889 | * http://getbootstrap.com/javascript/#scrollspy 1890 | * ======================================================================== 1891 | * Copyright 2011-2016 Twitter, Inc. 1892 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1893 | * ======================================================================== */ 1894 | 1895 | 1896 | +function ($) { 1897 | 'use strict'; 1898 | 1899 | // SCROLLSPY CLASS DEFINITION 1900 | // ========================== 1901 | 1902 | function ScrollSpy(element, options) { 1903 | this.$body = $(document.body) 1904 | this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) 1905 | this.options = $.extend({}, ScrollSpy.DEFAULTS, options) 1906 | this.selector = (this.options.target || '') + ' .nav li > a' 1907 | this.offsets = [] 1908 | this.targets = [] 1909 | this.activeTarget = null 1910 | this.scrollHeight = 0 1911 | 1912 | this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) 1913 | this.refresh() 1914 | this.process() 1915 | } 1916 | 1917 | ScrollSpy.VERSION = '3.3.7' 1918 | 1919 | ScrollSpy.DEFAULTS = { 1920 | offset: 10 1921 | } 1922 | 1923 | ScrollSpy.prototype.getScrollHeight = function () { 1924 | return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) 1925 | } 1926 | 1927 | ScrollSpy.prototype.refresh = function () { 1928 | var that = this 1929 | var offsetMethod = 'offset' 1930 | var offsetBase = 0 1931 | 1932 | this.offsets = [] 1933 | this.targets = [] 1934 | this.scrollHeight = this.getScrollHeight() 1935 | 1936 | if (!$.isWindow(this.$scrollElement[0])) { 1937 | offsetMethod = 'position' 1938 | offsetBase = this.$scrollElement.scrollTop() 1939 | } 1940 | 1941 | this.$body 1942 | .find(this.selector) 1943 | .map(function () { 1944 | var $el = $(this) 1945 | var href = $el.data('target') || $el.attr('href') 1946 | var $href = /^#./.test(href) && $(href) 1947 | 1948 | return ($href 1949 | && $href.length 1950 | && $href.is(':visible') 1951 | && [[$href[offsetMethod]().top + offsetBase, href]]) || null 1952 | }) 1953 | .sort(function (a, b) { return a[0] - b[0] }) 1954 | .each(function () { 1955 | that.offsets.push(this[0]) 1956 | that.targets.push(this[1]) 1957 | }) 1958 | } 1959 | 1960 | ScrollSpy.prototype.process = function () { 1961 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 1962 | var scrollHeight = this.getScrollHeight() 1963 | var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() 1964 | var offsets = this.offsets 1965 | var targets = this.targets 1966 | var activeTarget = this.activeTarget 1967 | var i 1968 | 1969 | if (this.scrollHeight != scrollHeight) { 1970 | this.refresh() 1971 | } 1972 | 1973 | if (scrollTop >= maxScroll) { 1974 | return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) 1975 | } 1976 | 1977 | if (activeTarget && scrollTop < offsets[0]) { 1978 | this.activeTarget = null 1979 | return this.clear() 1980 | } 1981 | 1982 | for (i = offsets.length; i--;) { 1983 | activeTarget != targets[i] 1984 | && scrollTop >= offsets[i] 1985 | && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) 1986 | && this.activate(targets[i]) 1987 | } 1988 | } 1989 | 1990 | ScrollSpy.prototype.activate = function (target) { 1991 | this.activeTarget = target 1992 | 1993 | this.clear() 1994 | 1995 | var selector = this.selector + 1996 | '[data-target="' + target + '"],' + 1997 | this.selector + '[href="' + target + '"]' 1998 | 1999 | var active = $(selector) 2000 | .parents('li') 2001 | .addClass('active') 2002 | 2003 | if (active.parent('.dropdown-menu').length) { 2004 | active = active 2005 | .closest('li.dropdown') 2006 | .addClass('active') 2007 | } 2008 | 2009 | active.trigger('activate.bs.scrollspy') 2010 | } 2011 | 2012 | ScrollSpy.prototype.clear = function () { 2013 | $(this.selector) 2014 | .parentsUntil(this.options.target, '.active') 2015 | .removeClass('active') 2016 | } 2017 | 2018 | 2019 | // SCROLLSPY PLUGIN DEFINITION 2020 | // =========================== 2021 | 2022 | function Plugin(option) { 2023 | return this.each(function () { 2024 | var $this = $(this) 2025 | var data = $this.data('bs.scrollspy') 2026 | var options = typeof option == 'object' && option 2027 | 2028 | if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) 2029 | if (typeof option == 'string') data[option]() 2030 | }) 2031 | } 2032 | 2033 | var old = $.fn.scrollspy 2034 | 2035 | $.fn.scrollspy = Plugin 2036 | $.fn.scrollspy.Constructor = ScrollSpy 2037 | 2038 | 2039 | // SCROLLSPY NO CONFLICT 2040 | // ===================== 2041 | 2042 | $.fn.scrollspy.noConflict = function () { 2043 | $.fn.scrollspy = old 2044 | return this 2045 | } 2046 | 2047 | 2048 | // SCROLLSPY DATA-API 2049 | // ================== 2050 | 2051 | $(window).on('load.bs.scrollspy.data-api', function () { 2052 | $('[data-spy="scroll"]').each(function () { 2053 | var $spy = $(this) 2054 | Plugin.call($spy, $spy.data()) 2055 | }) 2056 | }) 2057 | 2058 | }(jQuery); 2059 | 2060 | /* ======================================================================== 2061 | * Bootstrap: tab.js v3.3.7 2062 | * http://getbootstrap.com/javascript/#tabs 2063 | * ======================================================================== 2064 | * Copyright 2011-2016 Twitter, Inc. 2065 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2066 | * ======================================================================== */ 2067 | 2068 | 2069 | +function ($) { 2070 | 'use strict'; 2071 | 2072 | // TAB CLASS DEFINITION 2073 | // ==================== 2074 | 2075 | var Tab = function (element) { 2076 | // jscs:disable requireDollarBeforejQueryAssignment 2077 | this.element = $(element) 2078 | // jscs:enable requireDollarBeforejQueryAssignment 2079 | } 2080 | 2081 | Tab.VERSION = '3.3.7' 2082 | 2083 | Tab.TRANSITION_DURATION = 150 2084 | 2085 | Tab.prototype.show = function () { 2086 | var $this = this.element 2087 | var $ul = $this.closest('ul:not(.dropdown-menu)') 2088 | var selector = $this.data('target') 2089 | 2090 | if (!selector) { 2091 | selector = $this.attr('href') 2092 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 2093 | } 2094 | 2095 | if ($this.parent('li').hasClass('active')) return 2096 | 2097 | var $previous = $ul.find('.active:last a') 2098 | var hideEvent = $.Event('hide.bs.tab', { 2099 | relatedTarget: $this[0] 2100 | }) 2101 | var showEvent = $.Event('show.bs.tab', { 2102 | relatedTarget: $previous[0] 2103 | }) 2104 | 2105 | $previous.trigger(hideEvent) 2106 | $this.trigger(showEvent) 2107 | 2108 | if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return 2109 | 2110 | var $target = $(selector) 2111 | 2112 | this.activate($this.closest('li'), $ul) 2113 | this.activate($target, $target.parent(), function () { 2114 | $previous.trigger({ 2115 | type: 'hidden.bs.tab', 2116 | relatedTarget: $this[0] 2117 | }) 2118 | $this.trigger({ 2119 | type: 'shown.bs.tab', 2120 | relatedTarget: $previous[0] 2121 | }) 2122 | }) 2123 | } 2124 | 2125 | Tab.prototype.activate = function (element, container, callback) { 2126 | var $active = container.find('> .active') 2127 | var transition = callback 2128 | && $.support.transition 2129 | && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) 2130 | 2131 | function next() { 2132 | $active 2133 | .removeClass('active') 2134 | .find('> .dropdown-menu > .active') 2135 | .removeClass('active') 2136 | .end() 2137 | .find('[data-toggle="tab"]') 2138 | .attr('aria-expanded', false) 2139 | 2140 | element 2141 | .addClass('active') 2142 | .find('[data-toggle="tab"]') 2143 | .attr('aria-expanded', true) 2144 | 2145 | if (transition) { 2146 | element[0].offsetWidth // reflow for transition 2147 | element.addClass('in') 2148 | } else { 2149 | element.removeClass('fade') 2150 | } 2151 | 2152 | if (element.parent('.dropdown-menu').length) { 2153 | element 2154 | .closest('li.dropdown') 2155 | .addClass('active') 2156 | .end() 2157 | .find('[data-toggle="tab"]') 2158 | .attr('aria-expanded', true) 2159 | } 2160 | 2161 | callback && callback() 2162 | } 2163 | 2164 | $active.length && transition ? 2165 | $active 2166 | .one('bsTransitionEnd', next) 2167 | .emulateTransitionEnd(Tab.TRANSITION_DURATION) : 2168 | next() 2169 | 2170 | $active.removeClass('in') 2171 | } 2172 | 2173 | 2174 | // TAB PLUGIN DEFINITION 2175 | // ===================== 2176 | 2177 | function Plugin(option) { 2178 | return this.each(function () { 2179 | var $this = $(this) 2180 | var data = $this.data('bs.tab') 2181 | 2182 | if (!data) $this.data('bs.tab', (data = new Tab(this))) 2183 | if (typeof option == 'string') data[option]() 2184 | }) 2185 | } 2186 | 2187 | var old = $.fn.tab 2188 | 2189 | $.fn.tab = Plugin 2190 | $.fn.tab.Constructor = Tab 2191 | 2192 | 2193 | // TAB NO CONFLICT 2194 | // =============== 2195 | 2196 | $.fn.tab.noConflict = function () { 2197 | $.fn.tab = old 2198 | return this 2199 | } 2200 | 2201 | 2202 | // TAB DATA-API 2203 | // ============ 2204 | 2205 | var clickHandler = function (e) { 2206 | e.preventDefault() 2207 | Plugin.call($(this), 'show') 2208 | } 2209 | 2210 | $(document) 2211 | .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) 2212 | .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) 2213 | 2214 | }(jQuery); 2215 | 2216 | /* ======================================================================== 2217 | * Bootstrap: affix.js v3.3.7 2218 | * http://getbootstrap.com/javascript/#affix 2219 | * ======================================================================== 2220 | * Copyright 2011-2016 Twitter, Inc. 2221 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2222 | * ======================================================================== */ 2223 | 2224 | 2225 | +function ($) { 2226 | 'use strict'; 2227 | 2228 | // AFFIX CLASS DEFINITION 2229 | // ====================== 2230 | 2231 | var Affix = function (element, options) { 2232 | this.options = $.extend({}, Affix.DEFAULTS, options) 2233 | 2234 | this.$target = $(this.options.target) 2235 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 2236 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 2237 | 2238 | this.$element = $(element) 2239 | this.affixed = null 2240 | this.unpin = null 2241 | this.pinnedOffset = null 2242 | 2243 | this.checkPosition() 2244 | } 2245 | 2246 | Affix.VERSION = '3.3.7' 2247 | 2248 | Affix.RESET = 'affix affix-top affix-bottom' 2249 | 2250 | Affix.DEFAULTS = { 2251 | offset: 0, 2252 | target: window 2253 | } 2254 | 2255 | Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { 2256 | var scrollTop = this.$target.scrollTop() 2257 | var position = this.$element.offset() 2258 | var targetHeight = this.$target.height() 2259 | 2260 | if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false 2261 | 2262 | if (this.affixed == 'bottom') { 2263 | if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' 2264 | return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' 2265 | } 2266 | 2267 | var initializing = this.affixed == null 2268 | var colliderTop = initializing ? scrollTop : position.top 2269 | var colliderHeight = initializing ? targetHeight : height 2270 | 2271 | if (offsetTop != null && scrollTop <= offsetTop) return 'top' 2272 | if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' 2273 | 2274 | return false 2275 | } 2276 | 2277 | Affix.prototype.getPinnedOffset = function () { 2278 | if (this.pinnedOffset) return this.pinnedOffset 2279 | this.$element.removeClass(Affix.RESET).addClass('affix') 2280 | var scrollTop = this.$target.scrollTop() 2281 | var position = this.$element.offset() 2282 | return (this.pinnedOffset = position.top - scrollTop) 2283 | } 2284 | 2285 | Affix.prototype.checkPositionWithEventLoop = function () { 2286 | setTimeout($.proxy(this.checkPosition, this), 1) 2287 | } 2288 | 2289 | Affix.prototype.checkPosition = function () { 2290 | if (!this.$element.is(':visible')) return 2291 | 2292 | var height = this.$element.height() 2293 | var offset = this.options.offset 2294 | var offsetTop = offset.top 2295 | var offsetBottom = offset.bottom 2296 | var scrollHeight = Math.max($(document).height(), $(document.body).height()) 2297 | 2298 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 2299 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 2300 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 2301 | 2302 | var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) 2303 | 2304 | if (this.affixed != affix) { 2305 | if (this.unpin != null) this.$element.css('top', '') 2306 | 2307 | var affixType = 'affix' + (affix ? '-' + affix : '') 2308 | var e = $.Event(affixType + '.bs.affix') 2309 | 2310 | this.$element.trigger(e) 2311 | 2312 | if (e.isDefaultPrevented()) return 2313 | 2314 | this.affixed = affix 2315 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 2316 | 2317 | this.$element 2318 | .removeClass(Affix.RESET) 2319 | .addClass(affixType) 2320 | .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') 2321 | } 2322 | 2323 | if (affix == 'bottom') { 2324 | this.$element.offset({ 2325 | top: scrollHeight - height - offsetBottom 2326 | }) 2327 | } 2328 | } 2329 | 2330 | 2331 | // AFFIX PLUGIN DEFINITION 2332 | // ======================= 2333 | 2334 | function Plugin(option) { 2335 | return this.each(function () { 2336 | var $this = $(this) 2337 | var data = $this.data('bs.affix') 2338 | var options = typeof option == 'object' && option 2339 | 2340 | if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 2341 | if (typeof option == 'string') data[option]() 2342 | }) 2343 | } 2344 | 2345 | var old = $.fn.affix 2346 | 2347 | $.fn.affix = Plugin 2348 | $.fn.affix.Constructor = Affix 2349 | 2350 | 2351 | // AFFIX NO CONFLICT 2352 | // ================= 2353 | 2354 | $.fn.affix.noConflict = function () { 2355 | $.fn.affix = old 2356 | return this 2357 | } 2358 | 2359 | 2360 | // AFFIX DATA-API 2361 | // ============== 2362 | 2363 | $(window).on('load', function () { 2364 | $('[data-spy="affix"]').each(function () { 2365 | var $spy = $(this) 2366 | var data = $spy.data() 2367 | 2368 | data.offset = data.offset || {} 2369 | 2370 | if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom 2371 | if (data.offsetTop != null) data.offset.top = data.offsetTop 2372 | 2373 | Plugin.call($spy, data) 2374 | }) 2375 | }) 2376 | 2377 | }(jQuery); 2378 | --------------------------------------------------------------------------------