├── res ├── public │ └── config │ │ ├── styles.css │ │ ├── index.html │ │ ├── scripts.js │ │ ├── underscore-min.js │ │ ├── backbone-min.js │ │ ├── bootstrap.min.js │ │ └── jquery.min.js ├── RootResponse.xml └── config.yaml ├── src ├── ssdp.js ├── coreoutput.js └── harmony-span.js ├── LICENSE ├── .gitignore ├── package.json └── README.md /res/public/config/styles.css: -------------------------------------------------------------------------------- 1 | /* Show it is fixed to the top */ 2 | body { 3 | padding-top: 4.5rem; 4 | } 5 | 6 | table#buttonTable > tbody > tr > td:nth-child(3) { 7 | max-width: 400px; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | white-space: nowrap; 11 | } 12 | 13 | table#buttonTable > tbody > tr > td:nth-child(4) { 14 | max-width: 200px; 15 | overflow: hidden; 16 | text-overflow: ellipsis; 17 | white-space: nowrap; 18 | } 19 | 20 | tr.inactive td { 21 | color: grey; 22 | } -------------------------------------------------------------------------------- /src/ssdp.js: -------------------------------------------------------------------------------- 1 | const Server = require('node-ssdp').Server; 2 | const colorout = require("./coreoutput"); 3 | 4 | module.exports.run = async(httpServerUrl) => { 5 | ssdp = new Server({ 6 | "location": httpServerUrl, 7 | "udn": "uuid:roku:ecp:HARMONYSPAN", 8 | "ssdpSig": 'Server: Roku/9.3.0 UPnP/1.0 Roku/9.3.0' 9 | }); 10 | 11 | ssdp.addUSN("roku:ecp"); 12 | ssdp.start(); 13 | colorout.log("success", "[SSDP-Server] running and bound to all available network interfaces"); 14 | 15 | process.on('SIGINT', function() { 16 | colorout.log("info", "[SSDP-Server] Shutting down SSDP server."); 17 | ssdp.stop(); 18 | }); 19 | }; -------------------------------------------------------------------------------- /src/coreoutput.js: -------------------------------------------------------------------------------- 1 | const colors = require("colors/safe") 2 | 3 | module.exports.log = async (status, message) => 4 | { 5 | switch(status.toLowerCase()) 6 | { 7 | case "regular": 8 | console.log("[ ] " + message); 9 | break; 10 | case "debug": 11 | console.log("[" + colors.magenta("~") + "] " + message); 12 | break; 13 | case "success": 14 | console.log("[" + colors.green(">") + "] " + message); 15 | break; 16 | case "info": 17 | console.log("[" + colors.blue("i") + "] " + message); 18 | break; 19 | case "warning": 20 | console.log("[" + colors.yellow("!") + "] " + message); 21 | break; 22 | case "error": 23 | console.log("[" + colors.red("X") + "] " + message); 24 | break; 25 | } 26 | } -------------------------------------------------------------------------------- /res/RootResponse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webhook Bridge 7 | Roku 8 | http://www.github.com/ashifter/harmony-span/ 9 | Streaming Stick+ 10 | HARMONY-FREEDOM 11 | uuid:00000000-1337-0000-1337-000000000000 12 | 13 | 14 | -------------------------------------------------------------------------------- /res/public/config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | HarmonySpan Config UI 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Logan Lowe 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /res/config.yaml: -------------------------------------------------------------------------------- 1 | webserverConfig: 2 | bindHost: 0.0.0.0 3 | port: 8060 4 | mqttConfig: 5 | serverUrl: 'mqtt://localhost' 6 | serverUsername: harmony 7 | serverPassword: harmony 8 | enabled: false 9 | buttons: 10 | - id: 0 11 | name: Home 12 | action: GET 13 | url: 'https://www.example.com' 14 | httpHeaders: {} 15 | enabled: false 16 | - id: 1 17 | name: Rev 18 | action: GET 19 | url: 'https://www.example.com' 20 | httpHeaders: {} 21 | enabled: false 22 | - id: 2 23 | name: Fwd 24 | action: GET 25 | url: 'https://www.example.com' 26 | httpHeaders: {} 27 | enabled: false 28 | - id: 3 29 | name: Play 30 | action: GET 31 | url: 'https://www.example.com' 32 | httpHeaders: {} 33 | enabled: false 34 | - id: 4 35 | name: Select 36 | action: GET 37 | url: 'https://www.example.com' 38 | httpHeaders: {} 39 | enabled: false 40 | - id: 5 41 | name: Left 42 | action: GET 43 | url: 'https://www.example.com' 44 | httpHeaders: {} 45 | enabled: false 46 | - id: 6 47 | name: Right 48 | action: GET 49 | url: 'https://www.example.com' 50 | httpHeaders: {} 51 | enabled: false 52 | - id: 7 53 | name: Down 54 | action: GET 55 | url: 'https://www.example.com' 56 | httpHeaders: {} 57 | enabled: false 58 | - id: 8 59 | name: Up 60 | action: GET 61 | url: 'https://www.example.com' 62 | httpHeaders: {} 63 | enabled: false 64 | - id: 9 65 | name: Back 66 | action: GET 67 | url: 'https://www.example.com' 68 | httpHeaders: {} 69 | enabled: false 70 | - id: 10 71 | name: InstantReplay 72 | action: GET 73 | url: 'https://www.example.com' 74 | httpHeaders: {} 75 | enabled: false 76 | - id: 11 77 | name: Info 78 | action: GET 79 | url: 'https://www.example.com' 80 | httpHeaders: {} 81 | enabled: false 82 | - id: 12 83 | name: Backspace 84 | action: GET 85 | url: 'https://www.example.com' 86 | httpHeaders: {} 87 | enabled: false 88 | - id: 13 89 | name: Search 90 | action: GET 91 | url: 'https://www.example.com' 92 | httpHeaders: {} 93 | enabled: false 94 | - id: 14 95 | name: Enter 96 | action: GET 97 | url: 'https://www.example.com' 98 | httpHeaders: {} 99 | enabled: false 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harmonyspan", 3 | "version": "1.5.0", 4 | "description": "Control anything with your Logitech Harmony Hub.", 5 | "main": "harmony-span.js", 6 | "scripts": { 7 | "start": "node src/harmony-span.js", 8 | "build_all_from_win": "rmdir /q /s build && mkdir build && mkdir build\\HarmonySpan_Win_x64 && mkdir build\\HarmonySpan_Mac_x64 && mkdir build\\HarmonySpan_Linux_x64 && type harmony-span.js | nexe -t windows-x64-12.16.3 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o build/HarmonySpan_Win_x64/HarmonySpan_Win_x64 && type harmony-span.js | nexe -t mac-x64-12.9.1 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o build/HarmonySpan_Mac_x64/HarmonySpan_macOS_x64 && type harmony-span.js | nexe -t linux-x64-12.9.1 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o build/HarmonySpan_Linux_x64/HarmonySpan_Linux_x64 && copy RootResponse.xml build\\HarmonySpan_Win_x64 && copy config.json build\\HarmonySpan_Win_x64 && copy RootResponse.xml build\\HarmonySpan_Mac_x64 && copy config.json build\\HarmonySpan_Mac_x64 && copy RootResponse.xml build\\HarmonySpan_Linux_x64 && copy config.json build\\HarmonySpan_Linux_x64 && cd build && tar -c -f HarmonySpan_Win_x64.tar HarmonySpan_Win_x64 && tar -c -f HarmonySpan_Mac_x64.tar HarmonySpan_Mac_x64 && tar -c -f HarmonySpan_Linux_x64.tar HarmonySpan_Linux_x64", 9 | "build_win": "type harmony-span.js | nexe -t windows-x64-12.16.3 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o HarmonySpan_Win_x64", 10 | "build_mac": "cat harmony-span.js | nexe -t mac-x64-12.9.1 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o HarmonySpan_macOS_x64 & chmod +x ./HarmonySpan_macOS_x64", 11 | "build_linux": "cat harmony-span.js | nexe -t linux-x64-12.9.1 -r coreoutput.js -r ssdp.js -r config_utility/ -r node_modules/ -o HarmonySpan_Linux_x64 & chmod +x ./HarmonySpan_Linux_x64" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/ashifter/harmony-span.git" 16 | }, 17 | "keywords": [ 18 | "harmony", 19 | "smart", 20 | "home", 21 | "logitech", 22 | "universal", 23 | "remote", 24 | "network" 25 | ], 26 | "author": "Logan Lowe", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/ashifter/harmony-span/issues" 30 | }, 31 | "homepage": "https://github.com/ashifter/harmony-span#readme", 32 | "dependencies": { 33 | "colors": "^1.4.0", 34 | "express": "^4.17.1", 35 | "js-yaml": "^3.14.0", 36 | "mqtt": "^4.2.8", 37 | "node-ssdp": "^4.0.0", 38 | "node-webhooks": "^1.4.2", 39 | "node-yaml": "^4.0.1", 40 | "request": "^2.88.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | **All current harmony-span users are highly encouraged to migrate to [EcpEmuServer](https://github.com/AShifter/EcpEmuServer) as it reaches feature parity with harmony-span, as it offers the same basic functionality with many benefits.** 3 | 4 | harmony-span has not been maintained for a very long time and has several drawbacks that were mainly the result of poor design and framework choice. Trust me, EcpEmuServer is probably what you're looking for. 5 | 6 | # HarmonySpan 7 | a Node.JS application that allows you to trigger webhooks and issue MQTT-messages directly from a Logitech Harmony Hub and compatible remote. 8 | 9 | ## Quick Setup 10 | Have your Harmony Remote (or Hub), a MicroUSB cable, and the MyHarmony desktop software ready. **Compatibility issues are currently being investigated with the Mobile app. Your mileage may vary.** 11 | 12 | Download the latest binaries for your platform (HarmonySpan currently has pre-built binaries for Windows, macOS, and Linux available at https://github.com/AShifter/harmony-span/releases/). 13 | 14 | Run the executable you downloaded - if you get any popups asking for firewall permission in Windows, check both Private and Public networks permission. HarmonySpan will automatically find your first suitable network adapter. 15 | 16 | ![Windows Defender Firewall Notification](https://i.imgur.com/Ty6YcHM.png) 17 | 18 | On Linux/macOS machines you might have to run `chmod +x ./HarmonySpan__x64` on the binary itself to enable the executable flag for that file. 19 | 20 | Connect your Harmony Remote (or Hub if you don't have a remote with a MicroUSB port, like the Companion or Smart Control remotes) to your PC running the MyHarmony Software and scan for SSDP devices; 21 | 22 | ![MyHarmony Desktop Software, Scan for devices](https://i.imgur.com/GCnIPTr.png) 23 | 24 | a Roku device with the serial number HARMONY-FREEDOM should appear - this is your HarmonySpan server. Add it to your remote and **make sure you sync over USB.** After this step, you can sync wirelessly. You only need to sync over USB when you're initally adding HarmonySpan. 25 | 26 | ![MyHarmony Desktop Software, Harmony Span appearing](https://i.imgur.com/xSCdwNI.png) 27 | 28 | Unplug your remote and go to the Devices menu. HarmonySpan should appear - enter the HarmonySpan device and try pressing some buttons. You should see feedback in the terminal showing what buttons you're pressing. 29 | 30 | ![Terminal Feedback from HarmonySpan](https://i.imgur.com/zPqd60M.png) 31 | 32 | Navigate to the [HarmonySpan Configuration Utility page](http://localhost:8060/config) on your local network to start setting up HarmonySpan. 33 | 34 | ![HarmonySpan Configuration Utility](https://i.imgur.com/Z8eIQn7.png) 35 | 36 | For simple IFTTT webhooks, just paste the webhook URL in the URL field for each button you want to attach to a webhook, and press the Submit Settings button - HarmonySpan will automatically apply the new configuration without the need for a restart. 37 | 38 | To get IFTTT URLs and control your smart home devices, this video by Robert Cowan at https://youtu.be/XxH1q2Tcdis?t=573 (should begin at 9 minutes 33 seconds) will help you get started. 39 | 40 | In the Advanced Options menu (orange button), you can also add a body and header to the request if you want to talk directly to the API for things like LIFX bulbs. The ``query`` string is sent as the request body, and ``header`` as the request header. If you're not sure what this does, don't use it. 41 | 42 | At this point you can do whatever you want with the buttons - To make your lights turn on and off when you play and pause a movie, just add the HarmonySpan buttons that are linked to your 'lights on' and 'lights off' webhook URLs with the Pause and Play buttons on the activity you use to watch a movie. For more information, check Logitech's documentation on creating button sequences. https://support.myharmony.com/en-es/creating-button-sequences 43 | 44 | Click the Submit Settings button once you fill in all of the fields for the button you're working with. Switching buttons without submitting your changes will delete the new values you put in for the first button. 45 | 46 | ## How It Works 47 | Logitech allows you to control 'IP devices' over your local network (think Roku, NVIDIA SHIELD, Apple TV) rather than controlling them with IR. This works because each device broadcasts to a local Simple Service Discovery Protocol (SSDP) IP (``239.255.255.250``, chances are you can see traffic on it in your network with Wireshark) to let other devices in the network know that they can be controlled. 48 | 49 | HarmonySpan broadcasts SSDP messages as if it were a Roku device with External Control Protocol (ECP) support - this is how it's possible to add HarmonySpan to your Harmony Hub. The Harmony Hub sends ECP commands to a regular Roku to control it - but in HarmonySpan, we take those ECP commands, watch when they're triggered, and in turn trigger a webhook. This solution doesn't require a bluetooth keyboard or any special hardware emulation - we're talking over HTTP the whole way. 50 | 51 | ## See it in action 52 | TwoGuyzTech // Control ANYTHING with your Logitech Harmony remote! | Trigger Webhooks from Sequences 53 | https://www.youtube.com/watch?v=W59Ae5jUl2w 54 | 55 | Build Montage // DIM & BRIGHTEN Your Cinema Lights With PAUSE & PLAY In Your Home Theater! 56 | https://www.youtube.com/watch?v=Gqjma5_ZAYo 57 | -------------------------------------------------------------------------------- /src/harmony-span.js: -------------------------------------------------------------------------------- 1 | /// Import Modules /// 2 | const WebHooks = require("node-webhooks"); 3 | const fs = require("fs"); 4 | const Express = require("express"); 5 | const ssdp = require("./ssdp"); 6 | const colorout = require("./coreoutput"); 7 | const request = require("request"); 8 | const mqtt = require("mqtt"); 9 | const yaml = require("node-yaml"); 10 | const ip = require("ip"); 11 | 12 | const CONFIG_FILE = "../res/config.yaml"; 13 | const DEFAULT_HOST = "127.0.0.1"; 14 | const DEFAULT_PORT = 8060; 15 | 16 | let conf; 17 | let port; 18 | let host; 19 | let app; 20 | let server; 21 | let webHooks; 22 | let mqttClient; 23 | 24 | // Array of client's IP-addresses. Clients are Logitech Harmony Hubs 25 | let clients = []; 26 | 27 | function init() { 28 | conf = yaml.readSync(CONFIG_FILE); 29 | if (conf.webserverConfig.hasOwnProperty("bindHost") && conf.webserverConfig.bindHost != "") { 30 | host = conf.webserverConfig.bindHost; 31 | if (host == "0.0.0.0") { 32 | colorout.log("debug", "[Webserver] Binding to all available local IPs; one is " + ip.address()); 33 | } else { 34 | colorout.log("debug", "[Webserver] Found webserver-host in config-file. Using " + host); 35 | } 36 | } else { 37 | host = DEFAULT_HOST; 38 | colorout.log("debug", "[Webserver]: Found no webserver-host in config-file. Falling back to " + host); 39 | } 40 | 41 | if (conf.webserverConfig.hasOwnProperty("port") && conf.webserverConfig.port != "") { 42 | port = conf.webserverConfig.port; 43 | colorout.log("debug", "[Webserver] Found port in config-file. Using " + port); 44 | } else { 45 | port = DEFAULT_PORT; 46 | colorout.log("debug", "[Webserver] Found no port in config-file. Falling back to " + port); 47 | } 48 | 49 | app = Express(); 50 | 51 | webHooks = new WebHooks({ db: {} }); 52 | 53 | request.shouldKeepAlive = false; 54 | 55 | attachWebhooks(); 56 | 57 | configureWebserverRoutes(); 58 | 59 | if (host == "0.0.0.0") { 60 | ssdp.run("http://" + ip.address() + ":" + port + "/"); 61 | } else { 62 | ssdp.run("http://" + host + ":" + port + "/"); 63 | } 64 | 65 | process.on("SIGINT", () => { 66 | colorout.log("info", "[Webserver] Shutting down"); 67 | server.close(); 68 | process.exit(); 69 | }); 70 | 71 | if (host == "0.0.0.0") { 72 | // bind to all IPs. 73 | server = app.listen(port); 74 | } else { 75 | server = app.listen(port, host); 76 | } 77 | 78 | let mqttConfig = conf.mqttConfig; 79 | if (mqttConfig.hasOwnProperty("serverUrl") && mqttConfig.hasOwnProperty("serverUsername") && mqttConfig.hasOwnProperty("serverPassword") && mqttConfig.enabled == true) { 80 | connectMqttServer(); 81 | } 82 | 83 | if (host == "0.0.0.0") { 84 | colorout.log("info", "[Webserver] Configuration Menu available at http://" + ip.address() + ":" + port + "/config/"); 85 | } else { 86 | colorout.log("info", "[Webserver] Configuration Menu available at http://" + host + ":" + port + "/config/"); 87 | } 88 | } 89 | 90 | function attachWebhooks() { 91 | conf.buttons.forEach(function(button) { 92 | webHooks.remove(button.name); 93 | if (button.action == "POST") webHooks.add(button.name, button.url); 94 | }); 95 | } 96 | 97 | function connectMqttServer() { 98 | // TODO check if client is connected 99 | mqttClient = mqtt.connect(conf.mqttConfig.serverUrl, { 100 | username: conf.mqttConfig.serverUsername, 101 | password: conf.mqttConfig.serverPassword, 102 | reconnectPeriod: 0, 103 | connectTimeout: 5 * 1000 104 | }); 105 | mqttClient.on("connect", function() { 106 | colorout.log("debug", "[MQTT-Connection] connected to MQTT-server"); 107 | }) 108 | mqttClient.on("error", function() { 109 | colorout.log("error", "[MQTT-Connection] could not connect to MQTT-server"); 110 | }); 111 | } 112 | 113 | function configureWebserverRoutes() { 114 | // server static content from public directory (http://.../config/*) 115 | app.use(Express.static('res/public')); 116 | // automatically interpret incoming post messages as JSON if Content-Type=application/json 117 | app.use(Express.json()); 118 | 119 | // log all requests to debug 120 | app.use((req, res, next) => { 121 | colorout.log("debug", "[Webserver] " + req.method + " " + req.url + " from " + req.ip); 122 | next(); 123 | }); 124 | 125 | /// Send RootResponse.xml /// 126 | app.get('/', (req, res) => { 127 | if (!clients.includes(req.ip)) { 128 | clients.push(req.ip); 129 | colorout.log("info", "[Webserver] Logitech Hub at " + req.ip + " found me! Sending RootResponse.xml..."); 130 | } 131 | res.type('application/xml'); 132 | res.send(fs.readFileSync('res/RootResponse.xml', 'utf8')); 133 | res.end(); 134 | }); 135 | 136 | /// Button Event Handler /// 137 | app.post('/keypress/:action', function(req, res) { 138 | triggerAction(req.params['action']); 139 | res.end(); 140 | }); 141 | 142 | /// 143 | /// HarmonySpan Configuration API 144 | /// 145 | 146 | // get all buttons config 147 | app.get('/buttons/', function(req, res) { 148 | if (!req.accepts('json')) { 149 | res.sendStatus(415); 150 | } else { 151 | res.set('Content-Type', 'application/json'); 152 | res.send(JSON.stringify(conf.buttons)); 153 | } 154 | }); 155 | 156 | // get specific button config 157 | app.get('/buttons/:id', function(req, res) { 158 | if (!req.accepts('json')) { 159 | res.sendStatus(415); 160 | } else { 161 | let id = req.params['id']; 162 | if (!id.match(/[0-9]{1,2}/) || parseInt(id) < 0 || parseInt(id) > conf.buttons.length - 1) { 163 | res.send(404); 164 | } else { 165 | res.set('Content-Type', 'application/json'); 166 | res.send(conf.buttons[id]); 167 | } 168 | } 169 | }); 170 | 171 | // set specific button's config 172 | app.put('/buttons/:id', function(req, res) { 173 | // TODO validate 174 | let data = req.body; 175 | conf.buttons[data.id] = data; 176 | res.sendStatus(204); 177 | yaml.writeSync(CONFIG_FILE, conf); 178 | attachWebhooks(); 179 | colorout.log("debug", "[Core] Updated config file."); 180 | }); 181 | 182 | // get MQTT server config 183 | app.get('/mqttconfig', function(req, res) { 184 | res.contentType('application/json'); 185 | res.send(JSON.stringify(conf.mqttConfig)); 186 | }); 187 | 188 | // set MQTT server config 189 | app.post('/mqttconfig', function(req, res) { 190 | let data = req.body; 191 | let enabledBefore = conf.mqttConfig.enabled; 192 | let enabledAfter = data.enabled; 193 | if (enabledBefore != enabledAfter) { 194 | 195 | } 196 | conf.mqttConfig = data; 197 | yaml.writeSync(CONFIG_FILE, conf); 198 | colorout.log("debug", "[Core] Updated config file."); 199 | if (enabledBefore != enabledAfter) { 200 | if (enabledAfter) { 201 | connectMqttServer(); 202 | } else { 203 | mqttClient.end(); 204 | } 205 | } 206 | res.sendStatus(204); 207 | }); 208 | 209 | app.get('/mqttconnected', function(req, res) { 210 | res.set("Content-Type", "application/json"); 211 | res.send("{\"connected\": " + mqttClient.connected + " }"); 212 | }); 213 | } 214 | 215 | function triggerAction(buttonFunction) { 216 | let buttonIndex = getButtonIndex(buttonFunction); 217 | let button = conf.buttons[buttonIndex]; 218 | if (button.enabled) { 219 | switch (button.action) { 220 | case "GET": 221 | request(button.url, function(error, response, body) { 222 | if (error != null) { 223 | colorout.log("error", "[Webserver] HTTP GET: error: " + error); 224 | } else { 225 | colorout.log("debug", "[Webserver] HTTP GET: " + response.statusCode); 226 | } 227 | }); 228 | break; 229 | case "POST": 230 | webHooks.trigger(button.name, JSON.parse(button.postPayload), button.httpHeaders); 231 | // console.log(JSON.parse(button.postPayload)); 232 | break; 233 | case "MQTT": 234 | if (mqttClient && mqttClient.connected) { 235 | mqttClient.publish(button.mqttTopic, button.mqttMessage); 236 | colorout.log("debug", "[MQTT-Connection] Sent mqtt message"); 237 | } else { 238 | // TODO try reconnect 239 | colorout.log("error", "[MQTT-Connection] MQTT not connected"); 240 | } 241 | break; 242 | default: 243 | colorout.log("error", "[Core] Unknown action: " + button.action); 244 | } 245 | } else { 246 | colorout.log("debug", "[Core] Button disabled. Won't fire action"); 247 | } 248 | } 249 | 250 | function getButtonIndex(btnFunction) { 251 | let i; 252 | for (i = 0; i < conf.buttons.length; i++) { 253 | if (conf.buttons[i].name == btnFunction) return i; 254 | } 255 | return -1; 256 | } 257 | 258 | init(); 259 | -------------------------------------------------------------------------------- /res/public/config/scripts.js: -------------------------------------------------------------------------------- 1 | var Button = Backbone.Model.extend({ 2 | defaults: { 3 | id: -1, 4 | name: "", 5 | url: "", 6 | httpHeaders: {}, 7 | postPayload: "", 8 | mqttTopic: "", 9 | mqttMessage: "", 10 | action: "", 11 | enabled: false 12 | } 13 | }); 14 | 15 | var MqttConfig = Backbone.Model.extend({ 16 | url: '/mqttconfig' 17 | }); 18 | 19 | var ButtonCollection = Backbone.Collection.extend({ 20 | model: Button, 21 | url: '/buttons' 22 | }); 23 | 24 | var ButtonRowView = Backbone.View.extend({ 25 | tagName: "tr", 26 | className: function() { 27 | return this.model.get("enabled") ? "active" : "inactive"; 28 | }, 29 | template: _.template( 30 | "<%= id %>" + 31 | "<%= name %>" + 32 | "<% if (action ==\"GET\") { %>" + 33 | "[<%= action %>] <%= url %>\n" + 34 | "\n" + 35 | "<% } %>" + 36 | "<% if (action == \"POST\") { %>" + 37 | "[<%= action %>] <%= url %>\n" + 38 | "<%= postPayload %>\n" + 39 | "<% } %>" + 40 | "<% if (action == \"MQTT\") { %>" + 41 | "[<%= action %>] <%= mqttTopic %>\n" + 42 | "<%= mqttMessage %>\n" + 43 | "<% } %>" + 44 | "<%= enabled ? \"yes\" : \"no\" %>\n" + 45 | "\" class=\"edit btn btn-sm btn-primary\">edit" 46 | ), 47 | render: function() { 48 | this.$el.html(this.template(this.model.attributes)); 49 | return this; 50 | } 51 | }); 52 | 53 | var ButtonTableView = Backbone.View.extend({ 54 | tagName: "div", 55 | initialize: function() { 56 | this.listenTo(this.model, "reset", this.render); 57 | }, 58 | template: _.template( 59 | "
\n" + 60 | "\n" + 63 | "

Welcome to HarmonySpan

\n" + 64 | "

This service mimics a Roku device in a Logitech Harmony setup.
\n" + 65 | "Instead of controlling a Roku-device, you can assign custom actions to each of the functions available and then use a Harmony Remote to trigger those actions.

\n" + 66 | "

You can trigger remote actions by calling Webhooks, which helps integrating with services like IFTTT, Conrad Connect, Zapier.\n" + 67 | "You can also send MQTT-messages with each button-press. This helps you integrate with Home-Automation services like Node-RED. Lastly, you can run shell scripts.

" + 68 | "This tool helps you assign functions to actions." + 69 | "
\n" + 70 | "
" + 71 | "

Buttons Configuration

\n" + 72 | "
    " + 73 | "
    " + 74 | "" + 75 | " \n" + 76 | " \n" + 77 | " \n" + 78 | " \n" + 79 | " \n" + 80 | " \n" + 81 | " \n" + 82 | " \n" + 83 | " \n" + 84 | " \n" + 85 | " \n" + 86 | "
    IDFunctionActionPayload / MessageEnabled
    " 87 | ), 88 | render: function() { 89 | this.$el.empty(); 90 | this.$el.html(this.template()); 91 | let tbody = this.$el.find("tbody"); 92 | this.model.forEach((btn) => { 93 | var view = new ButtonRowView({ model: btn }); 94 | tbody.append(view.render().$el); 95 | }); 96 | return this; 97 | } 98 | }); 99 | 100 | var ButtonConfigView = Backbone.View.extend({ 101 | tagName: "div", 102 | initialize: function() { 103 | this.listenTo(this.model, "sync", this.handleSaved); 104 | this.mode = this.model.get("action"); 105 | }, 106 | template: _.template( 107 | "
    " + 108 | "

    Button Configuration <%= name %>

    \n" + 109 | "
      \n" + 110 | "
      \n" + 111 | "
      \n" + 112 | "
      \n" + 113 | "\n" + 114 | "\n" + 119 | "
      \n" + 120 | "<% if(mode==\"GET\" || mode==\"POST\") { %>" + 121 | "
      \n" + 122 | "\n" + 123 | "\" required>\n" + 124 | "
      \n" + 125 | "<% if(mode == \"POST\") { %>" + 126 | "
      \n" + 127 | "\n" + 128 | "\n" + 129 | "
      \n" + 130 | "<% } %>" + 131 | "
      \n" + 132 | "\n" + 133 | "\n" + 134 | "
      \n" + 135 | "<% } else if (mode == \"MQTT\") { %>" + 136 | "
      \n" + 137 | "\n" + 138 | "\" placeholder=\"E.g. harmony\" requird>\n" + 139 | "
      \n" + 140 | "
      \n" + 141 | "\n" + 142 | "\n" + 143 | "
      \n" + 144 | "<% } %>" + 145 | "
      \n" + 146 | " checked<% } %>>\n" + 147 | " \n" + 148 | "
      \n" + 149 | "\n" + 150 | "
      " 151 | ), 152 | events: { 153 | "change .select-action": "handleChangeAction", 154 | "submit form": "handleSubmit" 155 | }, 156 | handleChangeAction: function() { 157 | let value = this.$el.find("#action").val(); 158 | if (value !== this.mode) { 159 | this.mode = value; 160 | this.render(); 161 | } 162 | }, 163 | handleSubmit: function(event) { 164 | event.preventDefault(); 165 | let action = this.$el.find("#action").val().trim(); 166 | let id = this.model.get("id"); 167 | let name = this.model.get("name"); 168 | let enabled = this.$el.find("#enabled").prop('checked'); 169 | if (action == "GET" || action == "POST") { 170 | let headersString = this.$el.find("#httpHeaders").val().trim(); 171 | let headers = {}; 172 | if (headersString != "") { 173 | let lines = headersString.split("\n"); 174 | let obj = {}; 175 | let i; 176 | for (i = 0; i < lines.length; i++) { 177 | let tokens = lines[i].split("="); 178 | obj[tokens[0]] = tokens[1]; 179 | } 180 | headers = obj; 181 | } 182 | this.model.clear(); 183 | this.model.set({ 184 | id: id, 185 | name: name, 186 | action: action, 187 | url: this.$el.find("#url").val().trim(), 188 | httpHeaders: headers, 189 | enabled: enabled 190 | }); 191 | if (action == "POST") { 192 | this.model.set("postPayload", this.$el.find("#postPayload").val().trim()); 193 | } 194 | } else if (action == "MQTT") { 195 | this.model.clear(); 196 | this.model.set({ 197 | id: id, 198 | name: name, 199 | action: action, 200 | mqttTopic: this.$el.find("#mqttTopic").val().trim(), 201 | mqttMessage : this.$el.find("#mqttMessage").val().trim(), 202 | enabled: enabled 203 | }); 204 | } 205 | this.model.save(); 206 | }, 207 | handleSaved: function() { 208 | this.remove(); 209 | router.navigate("index", { trigger: true, replace: true }); 210 | }, 211 | render: function() { 212 | let attributes = this.model.attributes; 213 | attributes["mode"] = this.mode; 214 | this.$el.html(this.template(attributes)); 215 | return this; 216 | } 217 | }); 218 | 219 | var MqttConfigView = Backbone.View.extend({ 220 | tagName: "div", 221 | model: MqttConfig, 222 | template: _.template( 223 | "
      " + 224 | "

      MQTT Configuration

      \n" + 225 | "
      \n" + 226 | "
      \n" + 227 | "
      \n" + 228 | "\n" + 229 | "\" required pattern=\"mqtt://[a-z.-:0-9]{1,240}\">\n" + 230 | "
      \n" + 231 | "
      \n" + 232 | "\n" + 233 | "\" required>\n" + 234 | "
      \n" + 235 | "
      \n" + 236 | "\n" + 237 | "\" required>\n" + 238 | "
      \n" + 239 | "
      \n" + 240 | " checked<% } %>>\n" + 241 | " \n" + 242 | "
      \n" + 243 | "\n" + 244 | "
      " 245 | ), 246 | events: { 247 | "submit form": "handleSubmit" 248 | }, 249 | render: function() { 250 | this.$el.html(this.template(this.model.attributes)); 251 | return this; 252 | }, 253 | handleSubmit: function(event) { 254 | event.preventDefault(); 255 | this.model.set({ 256 | serverUrl: this.$el.find("#serverUrl").val(), 257 | serverUsername: this.$el.find("#serverUsername").val(), 258 | serverPassword: this.$el.find("#serverPassword").val(), 259 | enabled: this.$el.find("#mqttEnabled").prop('checked') 260 | }); 261 | this.listenToOnce(this.model, "sync", this.handleSave); 262 | navbar.startSpinning(); 263 | this.model.save(); 264 | }, 265 | handleSave: function() { 266 | navbar.stopSpinning(); 267 | router.navigate("index", { trigger: true, replace: true }); 268 | } 269 | }); 270 | 271 | var buttons = new ButtonCollection(); 272 | 273 | var buttonsTableView = new ButtonTableView({ 274 | model: buttons 275 | }); 276 | 277 | var router; 278 | 279 | var mqttConfig = new MqttConfig(); 280 | 281 | let container = $("#container"); 282 | 283 | var NavbarView = Backbone.View.extend({ 284 | tagName: "nav", 285 | className: "navbar navbar-expand-md navbar-dark fixed-top bg-dark", 286 | section: "buttons", 287 | template: _.template( 288 | "HarmonySpan\n" + 289 | "\n" + 293 | "
      \n" + 294 | "
        \n" + 295 | "
      • active<% } %>\">\n" + 296 | " Buttons (current)\n" + 297 | "
      • \n" + 298 | "
      • active<% } %>\">\n" + 299 | " MQTT-Config\n" + 300 | "
      • \n" + 301 | "
      \n" + 302 | "
      \n" + 303 | " Loading...\n" + 304 | "
      \n" + 305 | "
      " 306 | ), 307 | render: function() { 308 | this.$el.html(this.template({ section: this.section })); 309 | this.spinner = this.$el.find("div.spinner-border"); 310 | this.stopSpinning(); 311 | return this; 312 | }, 313 | changeSection: function(section) { 314 | this.section = section; 315 | this.render(); 316 | }, 317 | startSpinning: function() { 318 | this.spinner.show(); 319 | }, 320 | stopSpinning: function() { 321 | this.spinner.hide(); 322 | } 323 | }); 324 | 325 | var ButtonsRouter = Backbone.Router.extend({ 326 | routes: { 327 | "": "redirectToIndex", 328 | "index": "showIndex", 329 | "button/:id": "showButtonConfig", 330 | "mqtt": "showMqttConfig" 331 | }, 332 | 333 | redirectToIndex: function() { 334 | this.navigate("index", { trigger: true, replace: true }); 335 | }, 336 | 337 | showIndex: function() { 338 | buttons.fetch({ 339 | success: function(collection, response, options) { 340 | container.empty(); 341 | container.append(buttonsTableView.render().$el); 342 | navbar.changeSection("buttons"); 343 | } 344 | }); 345 | }, 346 | 347 | showButtonConfig(id) { 348 | if (buttons.length > 0) { 349 | var btn = buttons.get(parseInt(id)); 350 | var v = new ButtonConfigView({ 351 | model: btn 352 | }); 353 | container.empty(); 354 | container.append(v.render().$el); 355 | navbar.changeSection("buttons"); 356 | } else { 357 | this.navigate("index", { trigger: true, replace: true }); 358 | } 359 | }, 360 | 361 | showMqttConfig() { 362 | navbar.startSpinning(); 363 | let mqttConfigView = new MqttConfigView({ 364 | model: mqttConfig 365 | }); 366 | mqttConfig.fetch({ 367 | success: function(model, response, options) { 368 | container.html(mqttConfigView.render().$el); 369 | navbar.changeSection("mqtt"); 370 | } 371 | }); 372 | } 373 | }); 374 | 375 | var navbar = new NavbarView(); 376 | 377 | window.addEventListener('load', () => { 378 | router = new ButtonsRouter(); 379 | Backbone.history.start(); 380 | $("body").append(navbar.render().$el); 381 | }); 382 | -------------------------------------------------------------------------------- /res/public/config/underscore-min.js: -------------------------------------------------------------------------------- 1 | !function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n=n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){ 2 | // Underscore.js 1.11.0 3 | // https://underscorejs.org 4 | // (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 5 | // Underscore may be freely distributed under the MIT license. 6 | var n="1.11.0",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,i=t.push,o=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l=Array.isArray,s=Object.keys,p=Object.create,v=c&&ArrayBuffer.isView,h=isNaN,y=isFinite,g=!{toString:null}.propertyIsEnumerable("toString"),d=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function b(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u=0&&t<=m}}function K(n){return function(r){return null==r?void 0:r[n]}}var J=K("byteLength"),$=C(J),G=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var H=c?function(n){return v?v(n)&&!T(n):$(n)&&G.test(a.call(n))}:P(!1),Q=K("length"),X=C(Q);function Y(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},On=An(Sn),Mn=An(on(Sn)),En=rn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Bn=/(.)^/,Nn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Dn=/\\|'|\r|\n|\u2028|\u2029/g;function In(n){return"\\"+Nn[n]}var kn=0;function Tn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var i=pn(n.prototype),o=n.apply(i,u);return _(o)?o:i}var Vn=b((function(n,r){var t=Vn.placeholder,e=function(){for(var u=0,i=r.length,o=Array(i),a=0;a1)Fn(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var Pn=Vn(Ln,2);function Cn(n,r,t){r=jn(r,t);for(var e,u=Z(n),i=0,o=u.length;i0?0:u-1;i>=0&&i0?a=i>=0?i:Math.max(i+f,a):f=i>=0?Math.min(i+1,f):i+f+1;else if(t&&i&&f)return e[i=t(e,u)]===u?i:-1;if(u!=u)return(i=r(o.call(e,a,f),L))>=0?i+a:-1;for(i=n>0?a:f-1;i>=0&&i0?0:o-1;for(u||(e=r[i?i[a]:a],a+=n);a>=0&&a=3;return r(n,mn(t,u,4),e,i)}}var tr=rr(1),er=rr(-1);function ur(n,r,t){var e=[];return r=jn(r,t),Zn(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function ir(n,r,t){r=jn(r,t);for(var e=!X(n)&&Z(n),u=(e||n).length,i=0;i=0}var fr=b((function(n,r,t){var e,u;return U(r)?u=r:V(r)&&(e=r.slice(0,-1),r=r[r.length-1]),nr(n,(function(n){var i=u;if(!i){if(e&&e.length&&(n=gn(n,e)),null==n)return;i=n[r]}return null==i?i:i.apply(n,t)}))}));function cr(n,r){return nr(n,dn(r))}function lr(n,r,t){var e,u,i=-1/0,o=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=X(n)?n:un(n)).length;ai&&(i=e);else r=jn(r,t),Zn(n,(function(n,t,e){((u=r(n,t,e))>o||u===-1/0&&i===-1/0)&&(i=n,o=u)}));return i}function sr(n,r,t){if(null==r||t)return X(n)||(n=un(n)),n[wn(n.length-1)];var e=X(n)?vn(n):un(n),u=Q(e);r=Math.max(Math.min(r,u),0);for(var i=u-1,o=0;o1&&(e=mn(e,r[1])),r=en(n)):(e=mr,r=Fn(r,!1,!1),n=Object(n));for(var u=0,i=r.length;u1&&(t=r[1])):(r=nr(Fn(r,!1,!1),String),e=function(n,t){return!ar(r,t)}),br(n,e,t)}));function jr(n,r,t){return o.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function wr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:jr(n,n.length-r)}function xr(n,r,t){return o.call(n,null==r||t?1:r)}var Ar=b((function(n,r){return r=Fn(r,!0,!0),ur(n,(function(n){return!ar(r,n)}))})),Sr=b((function(n,r){return Ar(n,r)}));function Or(n,r,t,e){j(r)||(e=t,t=r,r=!1),null!=t&&(t=jn(t,e));for(var u=[],i=[],o=0,a=Q(n);or?(e&&(clearTimeout(e),e=null),a=c,o=n.apply(u,i),e||(u=i=null)):e||!1===t.trailing||(e=setTimeout(f,l)),o};return c.cancel=function(){clearTimeout(e),a=0,e=u=i=null},c},debounce:function(n,r,t){var e,u,i=function(r,t){e=null,t&&(u=n.apply(r,t))},o=b((function(o){if(e&&clearTimeout(e),t){var a=!e;e=setTimeout(i,r),a&&(u=n.apply(this,o))}else e=qn(i,r,this,o);return u}));return o.cancel=function(){clearTimeout(e),e=null},o},wrap:function(n,r){return Vn(r,n)},negate:zn,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:Ln,once:Pn,findKey:Cn,findIndex:Jn,findLastIndex:$n,sortedIndex:Gn,indexOf:Qn,lastIndexOf:Xn,find:Yn,detect:Yn,findWhere:function(n,r){return Yn(n,yn(r))},each:Zn,forEach:Zn,map:nr,collect:nr,reduce:tr,foldl:tr,inject:tr,reduceRight:er,foldr:er,filter:ur,select:ur,reject:function(n,r,t){return ur(n,zn(jn(r)),t)},every:ir,all:ir,some:or,any:or,contains:ar,includes:ar,include:ar,invoke:fr,pluck:cr,where:function(n,r){return ur(n,yn(r))},max:lr,min:function(n,r,t){var e,u,i=1/0,o=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=X(n)?n:un(n)).length;ae||void 0===t)return 1;if(tthis.length)r=this.length;if(r<0)r+=this.length+1;var s=[];var a=[];var o=[];var h=[];var u={};var l=e.add;var c=e.merge;var f=e.remove;var d=false;var v=this.comparator&&r==null&&e.sort!==false;var p=i.isString(this.comparator)?this.comparator:null;var g,m;for(m=0;m7);this._useHashChange=this._wantsHashChange&&this._hasHashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.history&&this.history.pushState);this._usePushState=this._wantsPushState&&this._hasPushState;this.fragment=this.getFragment();this.root=("/"+this.root+"/").replace(L,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe");this.iframe.src="javascript:0";this.iframe.style.display="none";this.iframe.tabIndex=-1;var n=document.body;var r=n.insertBefore(this.iframe,n.firstChild).contentWindow;r.document.open();r.document.close();r.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){s("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){s("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);B.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){if(!this.matchRoot())return false;t=this.fragment=this.getFragment(t);return i.some(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!B.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(t===""||t.charAt(0)==="?"){i=i.slice(0,-1)||"/"}var n=i+t;t=t.replace(W,"");var r=this.decodeFragment(t);if(this.fragment===r)return;this.fragment=r;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,n)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var s=this.iframe.contentWindow;if(!e.replace){s.document.open();s.document.close()}this._updateHash(s.location,t,e.replace)}}else{return this.location.assign(n)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var n=t.href.replace(/(javascript:|#).*$/,"");t.replace(n+"#"+e)}else{t.hash="#"+e}}});e.history=new B;var D=function(t,e){var n=this;var r;if(t&&i.has(t,"constructor")){r=t.constructor}else{r=function(){return n.apply(this,arguments)}}i.extend(r,n,e);r.prototype=i.create(n.prototype,t);r.prototype.constructor=r;r.__super__=n.prototype;return r};m.extend=_.extend=O.extend=T.extend=B.extend=D;var V=function(){throw new Error('A "url" property or function must be specified')};var G=function(t,e){var i=e.error;e.error=function(n){if(i)i.call(e.context,t,n,e);t.trigger("error",t,n,e)}};return e}); 2 | //# sourceMappingURL=backbone-min.map -------------------------------------------------------------------------------- /res/public/config/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.5.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};a.jQueryDetection(),e.fn.emulateTransitionEnd=r,e.event.special[a.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(t){if(e(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var l="alert",c=e.fn[l],h=function(){function t(t){this._element=t}var n=t.prototype;return n.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},n.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},n._getRootElement=function(t){var n=a.getSelectorFromElement(t),i=!1;return n&&(i=document.querySelector(n)),i||(i=e(t).closest(".alert")[0]),i},n._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},n._removeElement=function(t){var n=this;if(e(t).removeClass("show"),e(t).hasClass("fade")){var i=a.getTransitionDurationFromElement(t);e(t).one(a.TRANSITION_END,(function(e){return n._destroyElement(t,e)})).emulateTransitionEnd(i)}else this._destroyElement(t)},n._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.alert");o||(o=new t(this),i.data("bs.alert",o)),"close"===n&&o[n](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',h._handleDismiss(new h)),e.fn[l]=h._jQueryInterface,e.fn[l].Constructor=h,e.fn[l].noConflict=function(){return e.fn[l]=c,h._jQueryInterface};var u=e.fn.button,d=function(){function t(t){this._element=t}var n=t.prototype;return n.toggle=function(){var t=!0,n=!0,i=e(this._element).closest('[data-toggle="buttons"]')[0];if(i){var o=this._element.querySelector('input:not([type="hidden"])');if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains("active"))t=!1;else{var s=i.querySelector(".active");s&&e(s).removeClass("active")}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains("active")),e(o).trigger("change")),o.focus(),n=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(n&&this._element.setAttribute("aria-pressed",!this._element.classList.contains("active")),t&&e(this._element).toggleClass("active"))},n.dispose=function(){e.removeData(this._element,"bs.button"),this._element=null},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.button");i||(i=new t(this),e(this).data("bs.button",i)),"toggle"===n&&i[n]()}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=t.target,i=n;if(e(n).hasClass("btn")||(n=e(n).closest(".btn")[0]),!n||n.hasAttribute("disabled")||n.classList.contains("disabled"))t.preventDefault();else{var o=n.querySelector('input:not([type="hidden"])');if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();("LABEL"!==i.tagName||o&&"checkbox"!==o.type)&&d._jQueryInterface.call(e(n),"toggle")}})).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',(function(t){var n=e(t.target).closest(".btn")[0];e(n).toggleClass("focus",/^focus(in)?$/.test(t.type))})),e(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var n=t.prototype;return n.next=function(){this._isSliding||this._slide("next")},n.nextWhenVisible=function(){!document.hidden&&e(this._element).is(":visible")&&"hidden"!==e(this._element).css("visibility")&&this.next()},n.prev=function(){this._isSliding||this._slide("prev")},n.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(a.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},n.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},n.to=function(t){var n=this;this._activeElement=this._element.querySelector(".active.carousel-item");var i=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)e(this._element).one("slid.bs.carousel",(function(){return n.to(t)}));else{if(i===t)return this.pause(),void this.cycle();var o=t>i?"next":"prev";this._slide(o,this._items[t])}},n.dispose=function(){e(this._element).off(g),e.removeData(this._element,"bs.carousel"),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},n._getConfig=function(t){return t=s({},p,t),a.typeCheckConfig(f,t,_),t},n._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},n._addEventListeners=function(){var t=this;this._config.keyboard&&e(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&e(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},n._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var n=function(e){t._pointerEvent&&v[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},i=function(e){t._pointerEvent&&v[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};e(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(e(this._element).on("pointerdown.bs.carousel",(function(t){return n(t)})),e(this._element).on("pointerup.bs.carousel",(function(t){return i(t)})),this._element.classList.add("pointer-event")):(e(this._element).on("touchstart.bs.carousel",(function(t){return n(t)})),e(this._element).on("touchmove.bs.carousel",(function(e){return function(e){e.originalEvent.touches&&e.originalEvent.touches.length>1?t.touchDeltaX=0:t.touchDeltaX=e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),e(this._element).on("touchend.bs.carousel",(function(t){return i(t)})))}},n._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},n._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},n._getItemByDirection=function(t,e){var n="next"===t,i="prev"===t,o=this._getItemIndex(e),s=this._items.length-1;if((i&&0===o||n&&o===s)&&!this._config.wrap)return e;var r=(o+("prev"===t?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]},n._triggerSlideEvent=function(t,n){var i=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(".active.carousel-item")),s=e.Event("slide.bs.carousel",{relatedTarget:t,direction:n,from:o,to:i});return e(this._element).trigger(s),s},n._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var n=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));e(n).removeClass("active");var i=this._indicatorsElement.children[this._getItemIndex(t)];i&&e(i).addClass("active")}},n._slide=function(t,n){var i,o,s,r=this,l=this._element.querySelector(".active.carousel-item"),c=this._getItemIndex(l),h=n||l&&this._getItemByDirection(t,l),u=this._getItemIndex(h),d=Boolean(this._interval);if("next"===t?(i="carousel-item-left",o="carousel-item-next",s="left"):(i="carousel-item-right",o="carousel-item-prev",s="right"),h&&e(h).hasClass("active"))this._isSliding=!1;else if(!this._triggerSlideEvent(h,s).isDefaultPrevented()&&l&&h){this._isSliding=!0,d&&this.pause(),this._setActiveIndicatorElement(h);var f=e.Event("slid.bs.carousel",{relatedTarget:h,direction:s,from:c,to:u});if(e(this._element).hasClass("slide")){e(h).addClass(o),a.reflow(h),e(l).addClass(i),e(h).addClass(i);var g=parseInt(h.getAttribute("data-interval"),10);g?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=g):this._config.interval=this._config.defaultInterval||this._config.interval;var m=a.getTransitionDurationFromElement(l);e(l).one(a.TRANSITION_END,(function(){e(h).removeClass(i+" "+o).addClass("active"),e(l).removeClass("active "+o+" "+i),r._isSliding=!1,setTimeout((function(){return e(r._element).trigger(f)}),0)})).emulateTransitionEnd(m)}else e(l).removeClass("active"),e(h).addClass("active"),this._isSliding=!1,e(this._element).trigger(f);d&&this.cycle()}},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.carousel"),o=s({},p,e(this).data());"object"==typeof n&&(o=s({},o,n));var r="string"==typeof n?n:o.slide;if(i||(i=new t(this,o),e(this).data("bs.carousel",i)),"number"==typeof n)i.to(n);else if("string"==typeof r){if("undefined"==typeof i[r])throw new TypeError('No method named "'+r+'"');i[r]()}else o.interval&&o.ride&&(i.pause(),i.cycle())}))},t._dataApiClickHandler=function(n){var i=a.getSelectorFromElement(this);if(i){var o=e(i)[0];if(o&&e(o).hasClass("carousel")){var r=s({},e(o).data(),e(this).data()),l=this.getAttribute("data-slide-to");l&&(r.interval=!1),t._jQueryInterface.call(e(o),r),l&&e(o).data("bs.carousel").to(l),n.preventDefault()}}},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return p}}]),t}();e(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",b._dataApiClickHandler),e(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),n=0,i=t.length;n0&&(this._selector=r,this._triggerArray.push(s))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var n=t.prototype;return n.toggle=function(){e(this._element).hasClass("show")?this.hide():this.show()},n.show=function(){var n,i,o=this;if(!this._isTransitioning&&!e(this._element).hasClass("show")&&(this._parent&&0===(n=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains("collapse")}))).length&&(n=null),!(n&&(i=e(n).not(this._selector).data("bs.collapse"))&&i._isTransitioning))){var s=e.Event("show.bs.collapse");if(e(this._element).trigger(s),!s.isDefaultPrevented()){n&&(t._jQueryInterface.call(e(n).not(this._selector),"hide"),i||e(n).data("bs.collapse",null));var r=this._getDimension();e(this._element).removeClass("collapse").addClass("collapsing"),this._element.style[r]=0,this._triggerArray.length&&e(this._triggerArray).removeClass("collapsed").attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(r[0].toUpperCase()+r.slice(1)),c=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,(function(){e(o._element).removeClass("collapsing").addClass("collapse show"),o._element.style[r]="",o.setTransitioning(!1),e(o._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(c),this._element.style[r]=this._element[l]+"px"}}},n.hide=function(){var t=this;if(!this._isTransitioning&&e(this._element).hasClass("show")){var n=e.Event("hide.bs.collapse");if(e(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",a.reflow(this._element),e(this._element).addClass("collapsing").removeClass("collapse show");var o=this._triggerArray.length;if(o>0)for(var s=0;s0},i._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=s({},e.offsets,t._config.offset(e.offsets,t._element)||{}),e}:e.offset=this._config.offset,e},i._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),s({},t,this._config.popperConfig)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.dropdown");if(i||(i=new t(this,"object"==typeof n?n:null),e(this).data("bs.dropdown",i)),"string"==typeof n){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},t._clearMenus=function(n){if(!n||3!==n.which&&("keyup"!==n.type||9===n.which))for(var i=[].slice.call(document.querySelectorAll('[data-toggle="dropdown"]')),o=0,s=i.length;o0&&r--,40===n.which&&rdocument.documentElement.clientHeight;i||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");var o=a.getTransitionDurationFromElement(this._dialog);e(this._element).off(a.TRANSITION_END),e(this._element).one(a.TRANSITION_END,(function(){t._element.classList.remove("modal-static"),i||e(t._element).one(a.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,o)})).emulateTransitionEnd(o),this._element.focus()}else this.hide()},n._showElement=function(t){var n=this,i=e(this._element).hasClass("fade"),o=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),e(this._dialog).hasClass("modal-dialog-scrollable")&&o?o.scrollTop=0:this._element.scrollTop=0,i&&a.reflow(this._element),e(this._element).addClass("show"),this._config.focus&&this._enforceFocus();var s=e.Event("shown.bs.modal",{relatedTarget:t}),r=function(){n._config.focus&&n._element.focus(),n._isTransitioning=!1,e(n._element).trigger(s)};if(i){var l=a.getTransitionDurationFromElement(this._dialog);e(this._dialog).one(a.TRANSITION_END,r).emulateTransitionEnd(l)}else r()},n._enforceFocus=function(){var t=this;e(document).off("focusin.bs.modal").on("focusin.bs.modal",(function(n){document!==n.target&&t._element!==n.target&&0===e(t._element).has(n.target).length&&t._element.focus()}))},n._setEscapeEvent=function(){var t=this;this._isShown?e(this._element).on("keydown.dismiss.bs.modal",(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||e(this._element).off("keydown.dismiss.bs.modal")},n._setResizeEvent=function(){var t=this;this._isShown?e(window).on("resize.bs.modal",(function(e){return t.handleUpdate(e)})):e(window).off("resize.bs.modal")},n._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){e(document.body).removeClass("modal-open"),t._resetAdjustments(),t._resetScrollbar(),e(t._element).trigger("hidden.bs.modal")}))},n._removeBackdrop=function(){this._backdrop&&(e(this._backdrop).remove(),this._backdrop=null)},n._showBackdrop=function(t){var n=this,i=e(this._element).hasClass("fade")?"fade":"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",i&&this._backdrop.classList.add(i),e(this._backdrop).appendTo(document.body),e(this._element).on("click.dismiss.bs.modal",(function(t){n._ignoreBackdropClick?n._ignoreBackdropClick=!1:t.target===t.currentTarget&&n._triggerBackdropTransition()})),i&&a.reflow(this._backdrop),e(this._backdrop).addClass("show"),!t)return;if(!i)return void t();var o=a.getTransitionDurationFromElement(this._backdrop);e(this._backdrop).one(a.TRANSITION_END,t).emulateTransitionEnd(o)}else if(!this._isShown&&this._backdrop){e(this._backdrop).removeClass("show");var s=function(){n._removeBackdrop(),t&&t()};if(e(this._element).hasClass("fade")){var r=a.getTransitionDurationFromElement(this._backdrop);e(this._backdrop).one(a.TRANSITION_END,s).emulateTransitionEnd(r)}else s()}else t&&t()},n._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},n._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},n._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
      ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:L,popperConfig:null},K={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},X=function(){function t(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var i=t.prototype;return i.enable=function(){this._isEnabled=!0},i.disable=function(){this._isEnabled=!1},i.toggleEnabled=function(){this._isEnabled=!this._isEnabled},i.toggle=function(t){if(this._isEnabled)if(t){var n=this.constructor.DATA_KEY,i=e(t.currentTarget).data(n);i||(i=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(e(this.getTipElement()).hasClass("show"))return void this._leave(null,this);this._enter(null,this)}},i.dispose=function(){clearTimeout(this._timeout),e.removeData(this.element,this.constructor.DATA_KEY),e(this.element).off(this.constructor.EVENT_KEY),e(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&e(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},i.show=function(){var t=this;if("none"===e(this.element).css("display"))throw new Error("Please use show on visible elements");var i=e.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){e(this.element).trigger(i);var o=a.findShadowRoot(this.element),s=e.contains(null!==o?o:this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),l=a.getUID(this.constructor.NAME);r.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&e(r).addClass("fade");var c="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(c);this.addAttachmentClass(h);var u=this._getContainer();e(r).data(this.constructor.DATA_KEY,this),e.contains(this.element.ownerDocument.documentElement,this.tip)||e(r).appendTo(u),e(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,this._getPopperConfig(h)),e(r).addClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().on("mouseover",null,e.noop);var d=function(){t.config.animation&&t._fixTransition();var n=t._hoverState;t._hoverState=null,e(t.element).trigger(t.constructor.Event.SHOWN),"out"===n&&t._leave(null,t)};if(e(this.tip).hasClass("fade")){var f=a.getTransitionDurationFromElement(this.tip);e(this.tip).one(a.TRANSITION_END,d).emulateTransitionEnd(f)}else d()}},i.hide=function(t){var n=this,i=this.getTipElement(),o=e.Event(this.constructor.Event.HIDE),s=function(){"show"!==n._hoverState&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),e(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()};if(e(this.element).trigger(o),!o.isDefaultPrevented()){if(e(i).removeClass("show"),"ontouchstart"in document.documentElement&&e(document.body).children().off("mouseover",null,e.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,e(this.tip).hasClass("fade")){var r=a.getTransitionDurationFromElement(i);e(i).one(a.TRANSITION_END,s).emulateTransitionEnd(r)}else s();this._hoverState=""}},i.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},i.isWithContent=function(){return Boolean(this.getTitle())},i.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-tooltip-"+t)},i.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},i.setContent=function(){var t=this.getTipElement();this.setElementContent(e(t.querySelectorAll(".tooltip-inner")),this.getTitle()),e(t).removeClass("fade show")},i.setElementContent=function(t,n){"object"!=typeof n||!n.nodeType&&!n.jquery?this.config.html?(this.config.sanitize&&(n=Q(n,this.config.whiteList,this.config.sanitizeFn)),t.html(n)):t.text(n):this.config.html?e(n).parent().is(t)||t.empty().append(n):t.text(e(n).text())},i.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},i._getPopperConfig=function(t){var e=this;return s({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},i._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=s({},e.offsets,t.config.offset(e.offsets,t.element)||{}),e}:e.offset=this.config.offset,e},i._getContainer=function(){return!1===this.config.container?document.body:a.isElement(this.config.container)?e(this.config.container):e(document).find(this.config.container)},i._getAttachment=function(t){return V[t.toUpperCase()]},i._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(n){if("click"===n)e(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==n){var i="hover"===n?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o="hover"===n?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;e(t.element).on(i,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},e(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=s({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},i._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},i._enter=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e(n.getTipElement()).hasClass("show")||"show"===n._hoverState?n._hoverState="show":(clearTimeout(n._timeout),n._hoverState="show",n.config.delay&&n.config.delay.show?n._timeout=setTimeout((function(){"show"===n._hoverState&&n.show()}),n.config.delay.show):n.show())},i._leave=function(t,n){var i=this.constructor.DATA_KEY;(n=n||e(t.currentTarget).data(i))||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),e(t.currentTarget).data(i,n)),t&&(n._activeTrigger["focusout"===t.type?"focus":"hover"]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState="out",n.config.delay&&n.config.delay.hide?n._timeout=setTimeout((function(){"out"===n._hoverState&&n.hide()}),n.config.delay.hide):n.hide())},i._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},i._getConfig=function(t){var n=e(this.element).data();return Object.keys(n).forEach((function(t){-1!==M.indexOf(t)&&delete n[t]})),"number"==typeof(t=s({},this.constructor.Default,n,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a.typeCheckConfig(B,t,this.constructor.DefaultType),t.sanitize&&(t.template=Q(t.template,t.whiteList,t.sanitizeFn)),t},i._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},i._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(U);null!==n&&n.length&&t.removeClass(n.join(""))},i._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},i._fixTransition=function(){var t=this.getTipElement(),n=this.config.animation;null===t.getAttribute("x-placement")&&(e(t).removeClass("fade"),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},t._jQueryInterface=function(n){return this.each((function(){var i=e(this).data("bs.tooltip"),o="object"==typeof n&&n;if((i||!/dispose|hide/.test(n))&&(i||(i=new t(this,o),e(this).data("bs.tooltip",i)),"string"==typeof n)){if("undefined"==typeof i[n])throw new TypeError('No method named "'+n+'"');i[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return z}},{key:"NAME",get:function(){return B}},{key:"DATA_KEY",get:function(){return"bs.tooltip"}},{key:"Event",get:function(){return K}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return W}}]),t}();e.fn[B]=X._jQueryInterface,e.fn[B].Constructor=X,e.fn[B].noConflict=function(){return e.fn[B]=H,X._jQueryInterface};var Y="popover",$=e.fn[Y],J=new RegExp("(^|\\s)bs-popover\\S+","g"),G=s({},X.Default,{placement:"right",trigger:"click",content:"",template:'

      '}),Z=s({},X.DefaultType,{content:"(string|element|function)"}),tt={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},et=function(t){var n,i;function s(){return t.apply(this,arguments)||this}i=t,(n=s).prototype=Object.create(i.prototype),n.prototype.constructor=n,n.__proto__=i;var r=s.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-popover-"+t)},r.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},r.setContent=function(){var t=e(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(t.find(".popover-body"),n),t.removeClass("fade show")},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(J);null!==n&&n.length>0&&t.removeClass(n.join(""))},s._jQueryInterface=function(t){return this.each((function(){var n=e(this).data("bs.popover"),i="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new s(this,i),e(this).data("bs.popover",n)),"string"==typeof t)){if("undefined"==typeof n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},o(s,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return G}},{key:"NAME",get:function(){return Y}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return tt}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Z}}]),s}(X);e.fn[Y]=et._jQueryInterface,e.fn[Y].Constructor=et,e.fn[Y].noConflict=function(){return e.fn[Y]=$,et._jQueryInterface};var nt="scrollspy",it=e.fn[nt],ot={offset:10,method:"auto",target:""},st={offset:"number",method:"string",target:"(string|element)"},rt=function(){function t(t,n){var i=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(n),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,e(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return i._process(t)})),this.refresh(),this._process()}var n=t.prototype;return n.refresh=function(){var t=this,n=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?n:this._config.method,o="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var n,s=a.getSelectorFromElement(t);if(s&&(n=document.querySelector(s)),n){var r=n.getBoundingClientRect();if(r.width||r.height)return[e(n)[i]().top+o,s]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},n.dispose=function(){e.removeData(this._element,"bs.scrollspy"),e(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},n._getConfig=function(t){if("string"!=typeof(t=s({},ot,"object"==typeof t&&t?t:{})).target&&a.isElement(t.target)){var n=e(t.target).attr("id");n||(n=a.getUID(nt),e(t.target).attr("id",n)),t.target="#"+n}return a.typeCheckConfig(nt,t,st),t},n._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},n._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},n._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},n._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active":".active";i=(i=e.makeArray(e(o).find(r)))[i.length-1]}var l=e.Event("hide.bs.tab",{relatedTarget:this._element}),c=e.Event("show.bs.tab",{relatedTarget:i});if(i&&e(i).trigger(l),e(this._element).trigger(c),!c.isDefaultPrevented()&&!l.isDefaultPrevented()){s&&(n=document.querySelector(s)),this._activate(this._element,o);var h=function(){var n=e.Event("hidden.bs.tab",{relatedTarget:t._element}),o=e.Event("shown.bs.tab",{relatedTarget:i});e(i).trigger(n),e(t._element).trigger(o)};n?this._activate(n,n.parentNode,h):h()}}},n.dispose=function(){e.removeData(this._element,"bs.tab"),this._element=null},n._activate=function(t,n,i){var o=this,s=(!n||"UL"!==n.nodeName&&"OL"!==n.nodeName?e(n).children(".active"):e(n).find("> li > .active"))[0],r=i&&s&&e(s).hasClass("fade"),l=function(){return o._transitionComplete(t,s,i)};if(s&&r){var c=a.getTransitionDurationFromElement(s);e(s).removeClass("show").one(a.TRANSITION_END,l).emulateTransitionEnd(c)}else l()},n._transitionComplete=function(t,n,i){if(n){e(n).removeClass("active");var o=e(n.parentNode).find("> .dropdown-menu .active")[0];o&&e(o).removeClass("active"),"tab"===n.getAttribute("role")&&n.setAttribute("aria-selected",!1)}if(e(t).addClass("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),a.reflow(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&e(t.parentNode).hasClass("dropdown-menu")){var s=e(t).closest(".dropdown")[0];if(s){var r=[].slice.call(s.querySelectorAll(".dropdown-toggle"));e(r).addClass("active")}t.setAttribute("aria-expanded",!0)}i&&i()},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.tab");if(o||(o=new t(this),i.data("bs.tab",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n]()}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}}]),t}();e(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),lt._jQueryInterface.call(e(this),"show")})),e.fn.tab=lt._jQueryInterface,e.fn.tab.Constructor=lt,e.fn.tab.noConflict=function(){return e.fn.tab=at,lt._jQueryInterface};var ct=e.fn.toast,ht={animation:"boolean",autohide:"boolean",delay:"number"},ut={animation:!0,autohide:!0,delay:500},dt=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var n=t.prototype;return n.show=function(){var t=this,n=e.Event("show.bs.toast");if(e(this._element).trigger(n),!n.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var i=function(){t._element.classList.remove("showing"),t._element.classList.add("show"),e(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove("hide"),a.reflow(this._element),this._element.classList.add("showing"),this._config.animation){var o=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,i).emulateTransitionEnd(o)}else i()}},n.hide=function(){if(this._element.classList.contains("show")){var t=e.Event("hide.bs.toast");e(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},n.dispose=function(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),e(this._element).off("click.dismiss.bs.toast"),e.removeData(this._element,"bs.toast"),this._element=null,this._config=null},n._getConfig=function(t){return t=s({},ut,e(this._element).data(),"object"==typeof t&&t?t:{}),a.typeCheckConfig("toast",t,this.constructor.DefaultType),t},n._setListeners=function(){var t=this;e(this._element).on("click.dismiss.bs.toast",'[data-dismiss="toast"]',(function(){return t.hide()}))},n._close=function(){var t=this,n=function(){t._element.classList.add("hide"),e(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){var i=a.getTransitionDurationFromElement(this._element);e(this._element).one(a.TRANSITION_END,n).emulateTransitionEnd(i)}else n()},n._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(n){return this.each((function(){var i=e(this),o=i.data("bs.toast");if(o||(o=new t(this,"object"==typeof n&&n),i.data("bs.toast",o)),"string"==typeof n){if("undefined"==typeof o[n])throw new TypeError('No method named "'+n+'"');o[n](this)}}))},o(t,null,[{key:"VERSION",get:function(){return"4.5.2"}},{key:"DefaultType",get:function(){return ht}},{key:"Default",get:function(){return ut}}]),t}();e.fn.toast=dt._jQueryInterface,e.fn.toast.Constructor=dt,e.fn.toast.noConflict=function(){return e.fn.toast=ct,dt._jQueryInterface},t.Alert=h,t.Button=d,t.Carousel=b,t.Collapse=C,t.Dropdown=I,t.Modal=P,t.Popover=et,t.Scrollspy=rt,t.Tab=lt,t.Toast=dt,t.Tooltip=X,t.Util=a,Object.defineProperty(t,"__esModule",{value:!0})})); 7 | //# sourceMappingURL=bootstrap.min.js.map -------------------------------------------------------------------------------- /res/public/config/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0