diff --git a/Gunicorn.sh b/Gunicorn.sh new file mode 100755 index 0000000..c46f93a --- /dev/null +++ b/Gunicorn.sh @@ -0,0 +1,7 @@ +#!/bin/sh -e +cd /home/elforcer/FlaskSite/MainSite/ +echo "Подготовка..." +sh Prepare.sh + +echo "Запускаем сервер Gunicorn с MainSite..." +gunicorn -w 4 --timeout 900 --keep-alive 900 --keyfile=/etc/letsencrypt/live/elforcer.udm.ru/privkey.pem --certfile=/etc/letsencrypt/live/elforcer.udm.ru/cert.pem --bind 0.0.0.0:8009 StartServer:app diff --git a/InstallService.sh b/InstallService.sh new file mode 100644 index 0000000..83ec31a --- /dev/null +++ b/InstallService.sh @@ -0,0 +1,13 @@ +echo ' +[Unit] +Description=Запуск Основного сайта + +[Service] +ExecStart=/home/elforcer/FlaskSite/MainSite/Gunicorn.sh + +[Install] +WantedBy=multi-user.target +'>/etc/systemd/system/site_main.service + + +systemctl enable site_main.service diff --git a/Prepare.sh b/Prepare.sh new file mode 100644 index 0000000..19bc0d7 --- /dev/null +++ b/Prepare.sh @@ -0,0 +1,9 @@ +echo " Загружаем настройки в память..." +#Создаем папку для программы, если её не было +DIR="/dev/shm/mainsite" +if [ ! -d "$DIR" ]; then + mkdir $DIR +fi + +#Копируем настройки +#rsync -ah --delete db/Settings $DIR diff --git a/Restart.sh b/Restart.sh new file mode 100755 index 0000000..3a799aa --- /dev/null +++ b/Restart.sh @@ -0,0 +1,27 @@ +#!/bin/bash +###!/usr/bin/env bash + +echo "Мягко закрываем службу" +killall -s 15 gunicorn -r +x=7 +#P = if pgrep 'gunicorn'; +#echo $P +while [ $x -gt 0 ] && pgrep 'gunicorn' ; +do + sleep 1 + echo "Ожидание закрытия сек: $x" + x=$(( $x - 1 )) +done + + +if pgrep 'gunicorn'; +then + echo "Принудительно убиваем службу" + killall -s 9 gunicorn -r + sleep 3 +else + echo "Служба успешно закрыта" + +fi + +sh Gunicorn.sh diff --git a/StartFlask.sh b/StartFlask.sh new file mode 100755 index 0000000..1345c83 --- /dev/null +++ b/StartFlask.sh @@ -0,0 +1,2 @@ +python3 StartServer.py + diff --git a/StartServer.py b/StartServer.py new file mode 100755 index 0000000..0df378e --- /dev/null +++ b/StartServer.py @@ -0,0 +1,4 @@ +from app import app +if __name__ == '__main__': + #app.run(debug=True,ssl_context=('LinuxServer.crt','rootCA.key',3)) + app.run(debug=True) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..435a74c --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +#активация FLASK +#from flask import Flask + + +from flask import Flask, request, send_from_directory + +from flask import jsonify #для генерации JSON, не актуально +from flask_cors import CORS + +#from flask import redirect, url_for + + +#Активируем сжатие данных +from flask_compress import Compress +compress = Compress() +app = Flask(__name__, static_url_path='', static_folder='') +compress.init_app(app) + + + + +#Статика + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory('static/image', + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + +@app.route('/static/') +def send_from_statics(path): + return send_from_directory('static', path) + +@app.route('/temp/') +def send_from_temp(path): + return send_from_directory('temp', path) + + +# enable CORS +CORS(app) + +# sanity check route +@app.route('/ping', methods=['GET']) +def ping_pong(): + return jsonify('pong!') + +#@app.route('/') +#def static_file(path): +# return app.send_static_file(path)# + +#активируем файл конфигурации +app.config.from_object('config') + +#активируем Вьювер +from app import views + + + + diff --git a/app/middleware.py b/app/middleware.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/middleware.py @@ -0,0 +1 @@ + diff --git a/app/static/image/Documents.rar b/app/static/image/Documents.rar new file mode 100644 index 0000000..f7eb1f7 Binary files /dev/null and b/app/static/image/Documents.rar differ diff --git a/app/static/image/Small_Logo.png b/app/static/image/Small_Logo.png new file mode 100644 index 0000000..b3294a7 Binary files /dev/null and b/app/static/image/Small_Logo.png differ diff --git a/app/static/image/favicon.ico b/app/static/image/favicon.ico new file mode 100644 index 0000000..955d4a4 Binary files /dev/null and b/app/static/image/favicon.ico differ diff --git a/app/static/image/portfolio/color/1.jpg b/app/static/image/portfolio/color/1.jpg new file mode 100644 index 0000000..fbe3d40 Binary files /dev/null and b/app/static/image/portfolio/color/1.jpg differ diff --git a/app/static/image/portfolio/color/2.jpg b/app/static/image/portfolio/color/2.jpg new file mode 100644 index 0000000..05abd4d Binary files /dev/null and b/app/static/image/portfolio/color/2.jpg differ diff --git a/app/static/image/portfolio/color/3.jpg b/app/static/image/portfolio/color/3.jpg new file mode 100644 index 0000000..82f441a Binary files /dev/null and b/app/static/image/portfolio/color/3.jpg differ diff --git a/app/static/image/portfolio/color/4.jpg b/app/static/image/portfolio/color/4.jpg new file mode 100644 index 0000000..4112344 Binary files /dev/null and b/app/static/image/portfolio/color/4.jpg differ diff --git a/app/static/image/portfolio/color/5.jpg b/app/static/image/portfolio/color/5.jpg new file mode 100644 index 0000000..ba4f7be Binary files /dev/null and b/app/static/image/portfolio/color/5.jpg differ diff --git a/app/static/image/portfolio/color/6.jpg b/app/static/image/portfolio/color/6.jpg new file mode 100644 index 0000000..3153d24 Binary files /dev/null and b/app/static/image/portfolio/color/6.jpg differ diff --git a/app/static/image/portfolio/color/7.jpg b/app/static/image/portfolio/color/7.jpg new file mode 100644 index 0000000..fbae594 Binary files /dev/null and b/app/static/image/portfolio/color/7.jpg differ diff --git a/app/static/image/portfolio/metal/1.jpg b/app/static/image/portfolio/metal/1.jpg new file mode 100644 index 0000000..013d088 Binary files /dev/null and b/app/static/image/portfolio/metal/1.jpg differ diff --git a/app/static/image/portfolio/metal/2.jpg b/app/static/image/portfolio/metal/2.jpg new file mode 100644 index 0000000..7f041ca Binary files /dev/null and b/app/static/image/portfolio/metal/2.jpg differ diff --git a/app/static/image/portfolio/metal/3.jpg b/app/static/image/portfolio/metal/3.jpg new file mode 100644 index 0000000..7dd44dd Binary files /dev/null and b/app/static/image/portfolio/metal/3.jpg differ diff --git a/app/static/image/portfolio/metal/4.jpg b/app/static/image/portfolio/metal/4.jpg new file mode 100644 index 0000000..7b2ba44 Binary files /dev/null and b/app/static/image/portfolio/metal/4.jpg differ diff --git a/app/static/image/portfolio/metal/5.jpg b/app/static/image/portfolio/metal/5.jpg new file mode 100644 index 0000000..a96ad52 Binary files /dev/null and b/app/static/image/portfolio/metal/5.jpg differ diff --git a/app/static/image/portfolio/metal/6.jpg b/app/static/image/portfolio/metal/6.jpg new file mode 100644 index 0000000..0a5a4c9 Binary files /dev/null and b/app/static/image/portfolio/metal/6.jpg differ diff --git a/app/static/image/portfolio/metal/7.jpg b/app/static/image/portfolio/metal/7.jpg new file mode 100644 index 0000000..8259927 Binary files /dev/null and b/app/static/image/portfolio/metal/7.jpg differ diff --git a/app/static/image/portfolio/metal/8.jpg b/app/static/image/portfolio/metal/8.jpg new file mode 100644 index 0000000..1c8f135 Binary files /dev/null and b/app/static/image/portfolio/metal/8.jpg differ diff --git a/app/static/image/portfolio/metal/9.jpg b/app/static/image/portfolio/metal/9.jpg new file mode 100644 index 0000000..40144e8 Binary files /dev/null and b/app/static/image/portfolio/metal/9.jpg differ diff --git a/app/static/image/portfolio/stone/1.jpg b/app/static/image/portfolio/stone/1.jpg new file mode 100644 index 0000000..eb08236 Binary files /dev/null and b/app/static/image/portfolio/stone/1.jpg differ diff --git a/app/static/image/portfolio/stone/2.jpg b/app/static/image/portfolio/stone/2.jpg new file mode 100644 index 0000000..1c494e5 Binary files /dev/null and b/app/static/image/portfolio/stone/2.jpg differ diff --git a/app/static/image/portfolio/stone/3.jpg b/app/static/image/portfolio/stone/3.jpg new file mode 100644 index 0000000..2050617 Binary files /dev/null and b/app/static/image/portfolio/stone/3.jpg differ diff --git a/app/static/image/portfolio/stone/4.jpg b/app/static/image/portfolio/stone/4.jpg new file mode 100644 index 0000000..999d8dd Binary files /dev/null and b/app/static/image/portfolio/stone/4.jpg differ diff --git a/app/static/image/portfolio/stone/5.jpg b/app/static/image/portfolio/stone/5.jpg new file mode 100644 index 0000000..b100625 Binary files /dev/null and b/app/static/image/portfolio/stone/5.jpg differ diff --git a/app/static/image/portfolio/stone/6.jpg b/app/static/image/portfolio/stone/6.jpg new file mode 100644 index 0000000..5963df0 Binary files /dev/null and b/app/static/image/portfolio/stone/6.jpg differ diff --git a/app/static/image/portfolio/stone/7.jpg b/app/static/image/portfolio/stone/7.jpg new file mode 100644 index 0000000..d3e83cb Binary files /dev/null and b/app/static/image/portfolio/stone/7.jpg differ diff --git a/app/static/image/portfolio/stone/8.jpg b/app/static/image/portfolio/stone/8.jpg new file mode 100644 index 0000000..2706638 Binary files /dev/null and b/app/static/image/portfolio/stone/8.jpg differ diff --git a/app/static/node_modules/bootstrap/LICENSE b/app/static/node_modules/bootstrap/LICENSE new file mode 100644 index 0000000..daad872 --- /dev/null +++ b/app/static/node_modules/bootstrap/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2011-2019 Twitter, Inc. +Copyright (c) 2011-2019 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/static/node_modules/bootstrap/README.md b/app/static/node_modules/bootstrap/README.md new file mode 100644 index 0000000..9fa8f53 --- /dev/null +++ b/app/static/node_modules/bootstrap/README.md @@ -0,0 +1,214 @@ +

+ + Bootstrap logo + +

+ +

Bootstrap

+ +

+ Sleek, intuitive, and powerful front-end framework for faster and easier web development. +
+ Explore Bootstrap docs » +
+
+ Report bug + · + Request feature + · + Themes + · + Blog +

+ + +## Table of contents + +- [Quick start](#quick-start) +- [Status](#status) +- [What's included](#whats-included) +- [Bugs and feature requests](#bugs-and-feature-requests) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [Community](#community) +- [Versioning](#versioning) +- [Creators](#creators) +- [Thanks](#thanks) +- [Copyright and license](#copyright-and-license) + + +## Quick start + +Several quick start options are available: + +- [Download the latest release.](https://github.com/twbs/bootstrap/archive/v4.3.1.zip) +- Clone the repo: `git clone https://github.com/twbs/bootstrap.git` +- Install with [npm](https://www.npmjs.com/): `npm install bootstrap` +- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@4.3.1` +- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:4.3.1` +- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass` + +Read the [Getting started page](https://getbootstrap.com/docs/4.3/getting-started/introduction/) for information on the framework contents, templates and examples, and more. + + +## Status + +[![Slack](https://bootstrap-slack.herokuapp.com/badge.svg)](https://bootstrap-slack.herokuapp.com/) +[![Build Status](https://img.shields.io/travis/twbs/bootstrap/v4-dev.svg)](https://travis-ci.org/twbs/bootstrap) +[![npm version](https://img.shields.io/npm/v/bootstrap.svg)](https://www.npmjs.com/package/bootstrap) +[![Gem version](https://img.shields.io/gem/v/bootstrap.svg)](https://rubygems.org/gems/bootstrap) +[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue.svg)](https://atmospherejs.com/twbs/bootstrap) +[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap.svg)](https://packagist.org/packages/twbs/bootstrap) +[![NuGet](https://img.shields.io/nuget/vpre/bootstrap.svg)](https://www.nuget.org/packages/bootstrap/absoluteLatest) +[![peerDependencies Status](https://img.shields.io/david/peer/twbs/bootstrap.svg)](https://david-dm.org/twbs/bootstrap?type=peer) +[![devDependency Status](https://img.shields.io/david/dev/twbs/bootstrap.svg)](https://david-dm.org/twbs/bootstrap?type=dev) +[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/v4-dev.svg)](https://coveralls.io/github/twbs/bootstrap?branch=v4-dev) +[![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/v4-dev/dist/css/bootstrap.min.css?compression=gzip&label=CSS+gzip+size)](https://github.com/twbs/bootstrap/tree/v4-dev/dist/css/bootstrap.min.css) +[![JS gzip size](https://img.badgesize.io/twbs/bootstrap/v4-dev/dist/js/bootstrap.min.js?compression=gzip&label=JS+gzip+size)](https://github.com/twbs/bootstrap/tree/v4-dev/dist/js/bootstrap.min.js) +[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229) +[![Backers on Open Collective](https://opencollective.com/bootstrap/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/bootstrap/sponsors/badge.svg)](#sponsors) + + +## What's included + +Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this: + +```text +bootstrap/ +└── dist/ + ├── css/ + │ ├── bootstrap-grid.css + │ ├── bootstrap-grid.css.map + │ ├── bootstrap-grid.min.css + │ ├── bootstrap-grid.min.css.map + │ ├── bootstrap-reboot.css + │ ├── bootstrap-reboot.css.map + │ ├── bootstrap-reboot.min.css + │ ├── bootstrap-reboot.min.css.map + │ ├── bootstrap.css + │ ├── bootstrap.css.map + │ ├── bootstrap.min.css + │ └── bootstrap.min.css.map + └── js/ + ├── bootstrap.bundle.js + ├── bootstrap.bundle.js.map + ├── bootstrap.bundle.min.js + ├── bootstrap.bundle.min.js.map + ├── bootstrap.js + ├── bootstrap.js.map + ├── bootstrap.min.js + └── bootstrap.min.js.map +``` + +We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/). + + +## Bugs and feature requests + +Have a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new). + + +## Documentation + +Bootstrap's documentation, included in this repo in the root directory, is built with [Jekyll](https://jekyllrb.com/) and publicly hosted on GitHub Pages at . The docs may also be run locally. + +Documentation search is powered by [Algolia's DocSearch](https://community.algolia.com/docsearch/). Working on our search? Be sure to set `debug: true` in `site/docs/4.3/assets/js/src/search.js` file. + +### Running documentation locally + +1. Run through the [tooling setup](https://getbootstrap.com/docs/4.3/getting-started/build-tools/#tooling-setup) to install Jekyll (the site builder) and other Ruby dependencies with `bundle install`. +2. Run `npm install` to install Node.js dependencies. +3. Run `npm start` to compile CSS and JavaScript files, generate our docs, and watch for changes. +4. Open `http://localhost:9001` in your browser, and voilà. + +Learn more about using Jekyll by reading its [documentation](https://jekyllrb.com/docs/). + +### Documentation for previous releases + +- For v2.3.2: +- For v3.3.x: +- For v3.4.0: +- For v4.0.x: +- For v4.1.x: +- For v4.2.x: + +[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download. + + +## Contributing + +Please read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. + +Moreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/master/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo). + +Editor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at . + + +## Community + +Get updates on Bootstrap's development and chat with the project maintainers and community members. + +- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap). +- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/). +- Join [the official Slack room](https://bootstrap-slack.herokuapp.com/). +- Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##bootstrap` channel. +- Implementation help may be found at Stack Overflow (tagged [`bootstrap-4`](https://stackoverflow.com/questions/tagged/bootstrap-4)). +- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability. + + +## Versioning + +For transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](https://semver.org/). Sometimes we screw up, but we adhere to those rules whenever possible. + +See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com/) contain summaries of the most noteworthy changes made in each release. + + +## Creators + +**Mark Otto** + +- +- + +**Jacob Thornton** + +- +- + + +## Thanks + + + BrowserStack Logo + + +Thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers! + + +## Backers + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/bootstrap#backer)] + +[![Bakers](https://opencollective.com/bootstrap/backers.svg?width=890)](https://opencollective.com/bootstrap#backers) + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/bootstrap#sponsor)] + +[![](https://opencollective.com/bootstrap/sponsor/0/avatar.svg)](https://opencollective.com/bootstrap/sponsor/0/website) +[![](https://opencollective.com/bootstrap/sponsor/1/avatar.svg)](https://opencollective.com/bootstrap/sponsor/1/website) +[![](https://opencollective.com/bootstrap/sponsor/2/avatar.svg)](https://opencollective.com/bootstrap/sponsor/2/website) +[![](https://opencollective.com/bootstrap/sponsor/3/avatar.svg)](https://opencollective.com/bootstrap/sponsor/3/website) +[![](https://opencollective.com/bootstrap/sponsor/4/avatar.svg)](https://opencollective.com/bootstrap/sponsor/4/website) +[![](https://opencollective.com/bootstrap/sponsor/5/avatar.svg)](https://opencollective.com/bootstrap/sponsor/5/website) +[![](https://opencollective.com/bootstrap/sponsor/6/avatar.svg)](https://opencollective.com/bootstrap/sponsor/6/website) +[![](https://opencollective.com/bootstrap/sponsor/7/avatar.svg)](https://opencollective.com/bootstrap/sponsor/7/website) +[![](https://opencollective.com/bootstrap/sponsor/8/avatar.svg)](https://opencollective.com/bootstrap/sponsor/8/website) +[![](https://opencollective.com/bootstrap/sponsor/9/avatar.svg)](https://opencollective.com/bootstrap/sponsor/9/website) + + +## Copyright and license + +Code and documentation copyright 2011-2019 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/master/LICENSE). Docs released under [Creative Commons](https://github.com/twbs/bootstrap/blob/master/docs/LICENSE). diff --git a/app/static/node_modules/bootstrap/js/src/alert.js b/app/static/node_modules/bootstrap/js/src/alert.js new file mode 100644 index 0000000..64e8e38 --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/alert.js @@ -0,0 +1,179 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'alert' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.alert' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] + +const Selector = { + DISMISS : '[data-dismiss="alert"]' +} + +const Event = { + CLOSE : `close${EVENT_KEY}`, + CLOSED : `closed${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + ALERT : 'alert', + FADE : 'fade', + SHOW : 'show' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Alert { + constructor(element) { + this._element = element + } + + // Getters + + static get VERSION() { + return VERSION + } + + // Public + + close(element) { + let rootElement = this._element + if (element) { + rootElement = this._getRootElement(element) + } + + const customEvent = this._triggerCloseEvent(rootElement) + + if (customEvent.isDefaultPrevented()) { + return + } + + this._removeElement(rootElement) + } + + dispose() { + $.removeData(this._element, DATA_KEY) + this._element = null + } + + // Private + + _getRootElement(element) { + const selector = Util.getSelectorFromElement(element) + let parent = false + + if (selector) { + parent = document.querySelector(selector) + } + + if (!parent) { + parent = $(element).closest(`.${ClassName.ALERT}`)[0] + } + + return parent + } + + _triggerCloseEvent(element) { + const closeEvent = $.Event(Event.CLOSE) + + $(element).trigger(closeEvent) + return closeEvent + } + + _removeElement(element) { + $(element).removeClass(ClassName.SHOW) + + if (!$(element).hasClass(ClassName.FADE)) { + this._destroyElement(element) + return + } + + const transitionDuration = Util.getTransitionDurationFromElement(element) + + $(element) + .one(Util.TRANSITION_END, (event) => this._destroyElement(element, event)) + .emulateTransitionEnd(transitionDuration) + } + + _destroyElement(element) { + $(element) + .detach() + .trigger(Event.CLOSED) + .remove() + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + const $element = $(this) + let data = $element.data(DATA_KEY) + + if (!data) { + data = new Alert(this) + $element.data(DATA_KEY, data) + } + + if (config === 'close') { + data[config](this) + } + }) + } + + static _handleDismiss(alertInstance) { + return function (event) { + if (event) { + event.preventDefault() + } + + alertInstance.close(this) + } + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document).on( + Event.CLICK_DATA_API, + Selector.DISMISS, + Alert._handleDismiss(new Alert()) +) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Alert._jQueryInterface +$.fn[NAME].Constructor = Alert +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Alert._jQueryInterface +} + +export default Alert diff --git a/app/static/node_modules/bootstrap/js/src/button.js b/app/static/node_modules/bootstrap/js/src/button.js new file mode 100644 index 0000000..fcf8055 --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/button.js @@ -0,0 +1,171 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'button' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.button' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] + +const ClassName = { + ACTIVE : 'active', + BUTTON : 'btn', + FOCUS : 'focus' +} + +const Selector = { + DATA_TOGGLE_CARROT : '[data-toggle^="button"]', + DATA_TOGGLE : '[data-toggle="buttons"]', + INPUT : 'input:not([type="hidden"])', + ACTIVE : '.active', + BUTTON : '.btn' +} + +const Event = { + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, + FOCUS_BLUR_DATA_API : `focus${EVENT_KEY}${DATA_API_KEY} ` + + `blur${EVENT_KEY}${DATA_API_KEY}` +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Button { + constructor(element) { + this._element = element + } + + // Getters + + static get VERSION() { + return VERSION + } + + // Public + + toggle() { + let triggerChangeEvent = true + let addAriaPressed = true + const rootElement = $(this._element).closest( + Selector.DATA_TOGGLE + )[0] + + if (rootElement) { + const input = this._element.querySelector(Selector.INPUT) + + if (input) { + if (input.type === 'radio') { + if (input.checked && + this._element.classList.contains(ClassName.ACTIVE)) { + triggerChangeEvent = false + } else { + const activeElement = rootElement.querySelector(Selector.ACTIVE) + + if (activeElement) { + $(activeElement).removeClass(ClassName.ACTIVE) + } + } + } + + if (triggerChangeEvent) { + if (input.hasAttribute('disabled') || + rootElement.hasAttribute('disabled') || + input.classList.contains('disabled') || + rootElement.classList.contains('disabled')) { + return + } + input.checked = !this._element.classList.contains(ClassName.ACTIVE) + $(input).trigger('change') + } + + input.focus() + addAriaPressed = false + } + } + + if (addAriaPressed) { + this._element.setAttribute('aria-pressed', + !this._element.classList.contains(ClassName.ACTIVE)) + } + + if (triggerChangeEvent) { + $(this._element).toggleClass(ClassName.ACTIVE) + } + } + + dispose() { + $.removeData(this._element, DATA_KEY) + this._element = null + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + + if (!data) { + data = new Button(this) + $(this).data(DATA_KEY, data) + } + + if (config === 'toggle') { + data[config]() + } + }) + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document) + .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => { + event.preventDefault() + + let button = event.target + + if (!$(button).hasClass(ClassName.BUTTON)) { + button = $(button).closest(Selector.BUTTON) + } + + Button._jQueryInterface.call($(button), 'toggle') + }) + .on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => { + const button = $(event.target).closest(Selector.BUTTON)[0] + $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)) + }) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Button._jQueryInterface +$.fn[NAME].Constructor = Button +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Button._jQueryInterface +} + +export default Button diff --git a/app/static/node_modules/bootstrap/js/src/carousel.js b/app/static/node_modules/bootstrap/js/src/carousel.js new file mode 100644 index 0000000..36176dd --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/carousel.js @@ -0,0 +1,606 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'carousel' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.carousel' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] +const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key +const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key +const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch +const SWIPE_THRESHOLD = 40 + +const Default = { + interval : 5000, + keyboard : true, + slide : false, + pause : 'hover', + wrap : true, + touch : true +} + +const DefaultType = { + interval : '(number|boolean)', + keyboard : 'boolean', + slide : '(boolean|string)', + pause : '(string|boolean)', + wrap : 'boolean', + touch : 'boolean' +} + +const Direction = { + NEXT : 'next', + PREV : 'prev', + LEFT : 'left', + RIGHT : 'right' +} + +const Event = { + SLIDE : `slide${EVENT_KEY}`, + SLID : `slid${EVENT_KEY}`, + KEYDOWN : `keydown${EVENT_KEY}`, + MOUSEENTER : `mouseenter${EVENT_KEY}`, + MOUSELEAVE : `mouseleave${EVENT_KEY}`, + TOUCHSTART : `touchstart${EVENT_KEY}`, + TOUCHMOVE : `touchmove${EVENT_KEY}`, + TOUCHEND : `touchend${EVENT_KEY}`, + POINTERDOWN : `pointerdown${EVENT_KEY}`, + POINTERUP : `pointerup${EVENT_KEY}`, + DRAG_START : `dragstart${EVENT_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + CAROUSEL : 'carousel', + ACTIVE : 'active', + SLIDE : 'slide', + RIGHT : 'carousel-item-right', + LEFT : 'carousel-item-left', + NEXT : 'carousel-item-next', + PREV : 'carousel-item-prev', + ITEM : 'carousel-item', + POINTER_EVENT : 'pointer-event' +} + +const Selector = { + ACTIVE : '.active', + ACTIVE_ITEM : '.active.carousel-item', + ITEM : '.carousel-item', + ITEM_IMG : '.carousel-item img', + NEXT_PREV : '.carousel-item-next, .carousel-item-prev', + INDICATORS : '.carousel-indicators', + DATA_SLIDE : '[data-slide], [data-slide-to]', + DATA_RIDE : '[data-ride="carousel"]' +} + +const PointerType = { + TOUCH : 'touch', + PEN : 'pen' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ +class Carousel { + constructor(element, config) { + this._items = null + this._interval = null + this._activeElement = null + this._isPaused = false + this._isSliding = false + this.touchTimeout = null + this.touchStartX = 0 + this.touchDeltaX = 0 + + this._config = this._getConfig(config) + this._element = element + this._indicatorsElement = this._element.querySelector(Selector.INDICATORS) + this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0 + this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent) + + this._addEventListeners() + } + + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + // Public + + next() { + if (!this._isSliding) { + this._slide(Direction.NEXT) + } + } + + nextWhenVisible() { + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && + ($(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden')) { + this.next() + } + } + + prev() { + if (!this._isSliding) { + this._slide(Direction.PREV) + } + } + + pause(event) { + if (!event) { + this._isPaused = true + } + + if (this._element.querySelector(Selector.NEXT_PREV)) { + Util.triggerTransitionEnd(this._element) + this.cycle(true) + } + + clearInterval(this._interval) + this._interval = null + } + + cycle(event) { + if (!event) { + this._isPaused = false + } + + if (this._interval) { + clearInterval(this._interval) + this._interval = null + } + + if (this._config.interval && !this._isPaused) { + this._interval = setInterval( + (document.visibilityState ? this.nextWhenVisible : this.next).bind(this), + this._config.interval + ) + } + } + + to(index) { + this._activeElement = this._element.querySelector(Selector.ACTIVE_ITEM) + + const activeIndex = this._getItemIndex(this._activeElement) + + if (index > this._items.length - 1 || index < 0) { + return + } + + if (this._isSliding) { + $(this._element).one(Event.SLID, () => this.to(index)) + return + } + + if (activeIndex === index) { + this.pause() + this.cycle() + return + } + + const direction = index > activeIndex + ? Direction.NEXT + : Direction.PREV + + this._slide(direction, this._items[index]) + } + + dispose() { + $(this._element).off(EVENT_KEY) + $.removeData(this._element, DATA_KEY) + + this._items = null + this._config = null + this._element = null + this._interval = null + this._isPaused = null + this._isSliding = null + this._activeElement = null + this._indicatorsElement = null + } + + // Private + + _getConfig(config) { + config = { + ...Default, + ...config + } + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + + _handleSwipe() { + const absDeltax = Math.abs(this.touchDeltaX) + + if (absDeltax <= SWIPE_THRESHOLD) { + return + } + + const direction = absDeltax / this.touchDeltaX + + // swipe left + if (direction > 0) { + this.prev() + } + + // swipe right + if (direction < 0) { + this.next() + } + } + + _addEventListeners() { + if (this._config.keyboard) { + $(this._element) + .on(Event.KEYDOWN, (event) => this._keydown(event)) + } + + if (this._config.pause === 'hover') { + $(this._element) + .on(Event.MOUSEENTER, (event) => this.pause(event)) + .on(Event.MOUSELEAVE, (event) => this.cycle(event)) + } + + if (this._config.touch) { + this._addTouchEventListeners() + } + } + + _addTouchEventListeners() { + if (!this._touchSupported) { + return + } + + const start = (event) => { + if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { + this.touchStartX = event.originalEvent.clientX + } else if (!this._pointerEvent) { + this.touchStartX = event.originalEvent.touches[0].clientX + } + } + + const move = (event) => { + // ensure swiping with one touch and not pinching + if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { + this.touchDeltaX = 0 + } else { + this.touchDeltaX = event.originalEvent.touches[0].clientX - this.touchStartX + } + } + + const end = (event) => { + if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { + this.touchDeltaX = event.originalEvent.clientX - this.touchStartX + } + + this._handleSwipe() + if (this._config.pause === 'hover') { + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + + this.pause() + if (this.touchTimeout) { + clearTimeout(this.touchTimeout) + } + this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval) + } + } + + $(this._element.querySelectorAll(Selector.ITEM_IMG)).on(Event.DRAG_START, (e) => e.preventDefault()) + if (this._pointerEvent) { + $(this._element).on(Event.POINTERDOWN, (event) => start(event)) + $(this._element).on(Event.POINTERUP, (event) => end(event)) + + this._element.classList.add(ClassName.POINTER_EVENT) + } else { + $(this._element).on(Event.TOUCHSTART, (event) => start(event)) + $(this._element).on(Event.TOUCHMOVE, (event) => move(event)) + $(this._element).on(Event.TOUCHEND, (event) => end(event)) + } + } + + _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return + } + + switch (event.which) { + case ARROW_LEFT_KEYCODE: + event.preventDefault() + this.prev() + break + case ARROW_RIGHT_KEYCODE: + event.preventDefault() + this.next() + break + default: + } + } + + _getItemIndex(element) { + this._items = element && element.parentNode + ? [].slice.call(element.parentNode.querySelectorAll(Selector.ITEM)) + : [] + return this._items.indexOf(element) + } + + _getItemByDirection(direction, activeElement) { + const isNextDirection = direction === Direction.NEXT + const isPrevDirection = direction === Direction.PREV + const activeIndex = this._getItemIndex(activeElement) + const lastItemIndex = this._items.length - 1 + const isGoingToWrap = isPrevDirection && activeIndex === 0 || + isNextDirection && activeIndex === lastItemIndex + + if (isGoingToWrap && !this._config.wrap) { + return activeElement + } + + const delta = direction === Direction.PREV ? -1 : 1 + const itemIndex = (activeIndex + delta) % this._items.length + + return itemIndex === -1 + ? this._items[this._items.length - 1] : this._items[itemIndex] + } + + _triggerSlideEvent(relatedTarget, eventDirectionName) { + const targetIndex = this._getItemIndex(relatedTarget) + const fromIndex = this._getItemIndex(this._element.querySelector(Selector.ACTIVE_ITEM)) + const slideEvent = $.Event(Event.SLIDE, { + relatedTarget, + direction: eventDirectionName, + from: fromIndex, + to: targetIndex + }) + + $(this._element).trigger(slideEvent) + + return slideEvent + } + + _setActiveIndicatorElement(element) { + if (this._indicatorsElement) { + const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector.ACTIVE)) + $(indicators) + .removeClass(ClassName.ACTIVE) + + const nextIndicator = this._indicatorsElement.children[ + this._getItemIndex(element) + ] + + if (nextIndicator) { + $(nextIndicator).addClass(ClassName.ACTIVE) + } + } + } + + _slide(direction, element) { + const activeElement = this._element.querySelector(Selector.ACTIVE_ITEM) + const activeElementIndex = this._getItemIndex(activeElement) + const nextElement = element || activeElement && + this._getItemByDirection(direction, activeElement) + const nextElementIndex = this._getItemIndex(nextElement) + const isCycling = Boolean(this._interval) + + let directionalClassName + let orderClassName + let eventDirectionName + + if (direction === Direction.NEXT) { + directionalClassName = ClassName.LEFT + orderClassName = ClassName.NEXT + eventDirectionName = Direction.LEFT + } else { + directionalClassName = ClassName.RIGHT + orderClassName = ClassName.PREV + eventDirectionName = Direction.RIGHT + } + + if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) { + this._isSliding = false + return + } + + const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName) + if (slideEvent.isDefaultPrevented()) { + return + } + + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + return + } + + this._isSliding = true + + if (isCycling) { + this.pause() + } + + this._setActiveIndicatorElement(nextElement) + + const slidEvent = $.Event(Event.SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }) + + if ($(this._element).hasClass(ClassName.SLIDE)) { + $(nextElement).addClass(orderClassName) + + Util.reflow(nextElement) + + $(activeElement).addClass(directionalClassName) + $(nextElement).addClass(directionalClassName) + + const nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10) + if (nextElementInterval) { + this._config.defaultInterval = this._config.defaultInterval || this._config.interval + this._config.interval = nextElementInterval + } else { + this._config.interval = this._config.defaultInterval || this._config.interval + } + + const transitionDuration = Util.getTransitionDurationFromElement(activeElement) + + $(activeElement) + .one(Util.TRANSITION_END, () => { + $(nextElement) + .removeClass(`${directionalClassName} ${orderClassName}`) + .addClass(ClassName.ACTIVE) + + $(activeElement).removeClass(`${ClassName.ACTIVE} ${orderClassName} ${directionalClassName}`) + + this._isSliding = false + + setTimeout(() => $(this._element).trigger(slidEvent), 0) + }) + .emulateTransitionEnd(transitionDuration) + } else { + $(activeElement).removeClass(ClassName.ACTIVE) + $(nextElement).addClass(ClassName.ACTIVE) + + this._isSliding = false + $(this._element).trigger(slidEvent) + } + + if (isCycling) { + this.cycle() + } + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = { + ...Default, + ...$(this).data() + } + + if (typeof config === 'object') { + _config = { + ..._config, + ...config + } + } + + const action = typeof config === 'string' ? config : _config.slide + + if (!data) { + data = new Carousel(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'number') { + data.to(config) + } else if (typeof action === 'string') { + if (typeof data[action] === 'undefined') { + throw new TypeError(`No method named "${action}"`) + } + data[action]() + } else if (_config.interval && _config.ride) { + data.pause() + data.cycle() + } + }) + } + + static _dataApiClickHandler(event) { + const selector = Util.getSelectorFromElement(this) + + if (!selector) { + return + } + + const target = $(selector)[0] + + if (!target || !$(target).hasClass(ClassName.CAROUSEL)) { + return + } + + const config = { + ...$(target).data(), + ...$(this).data() + } + const slideIndex = this.getAttribute('data-slide-to') + + if (slideIndex) { + config.interval = false + } + + Carousel._jQueryInterface.call($(target), config) + + if (slideIndex) { + $(target).data(DATA_KEY).to(slideIndex) + } + + event.preventDefault() + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document) + .on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler) + +$(window).on(Event.LOAD_DATA_API, () => { + const carousels = [].slice.call(document.querySelectorAll(Selector.DATA_RIDE)) + for (let i = 0, len = carousels.length; i < len; i++) { + const $carousel = $(carousels[i]) + Carousel._jQueryInterface.call($carousel, $carousel.data()) + } +}) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Carousel._jQueryInterface +$.fn[NAME].Constructor = Carousel +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Carousel._jQueryInterface +} + +export default Carousel diff --git a/app/static/node_modules/bootstrap/js/src/collapse.js b/app/static/node_modules/bootstrap/js/src/collapse.js new file mode 100644 index 0000000..10df450 --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/collapse.js @@ -0,0 +1,402 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'collapse' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.collapse' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] + +const Default = { + toggle : true, + parent : '' +} + +const DefaultType = { + toggle : 'boolean', + parent : '(string|element)' +} + +const Event = { + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + SHOW : 'show', + COLLAPSE : 'collapse', + COLLAPSING : 'collapsing', + COLLAPSED : 'collapsed' +} + +const Dimension = { + WIDTH : 'width', + HEIGHT : 'height' +} + +const Selector = { + ACTIVES : '.show, .collapsing', + DATA_TOGGLE : '[data-toggle="collapse"]' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Collapse { + constructor(element, config) { + this._isTransitioning = false + this._element = element + this._config = this._getConfig(config) + this._triggerArray = [].slice.call(document.querySelectorAll( + `[data-toggle="collapse"][href="#${element.id}"],` + + `[data-toggle="collapse"][data-target="#${element.id}"]` + )) + + const toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE)) + for (let i = 0, len = toggleList.length; i < len; i++) { + const elem = toggleList[i] + const selector = Util.getSelectorFromElement(elem) + const filterElement = [].slice.call(document.querySelectorAll(selector)) + .filter((foundElem) => foundElem === element) + + if (selector !== null && filterElement.length > 0) { + this._selector = selector + this._triggerArray.push(elem) + } + } + + this._parent = this._config.parent ? this._getParent() : null + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._element, this._triggerArray) + } + + if (this._config.toggle) { + this.toggle() + } + } + + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + // Public + + toggle() { + if ($(this._element).hasClass(ClassName.SHOW)) { + this.hide() + } else { + this.show() + } + } + + show() { + if (this._isTransitioning || + $(this._element).hasClass(ClassName.SHOW)) { + return + } + + let actives + let activesData + + if (this._parent) { + actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES)) + .filter((elem) => { + if (typeof this._config.parent === 'string') { + return elem.getAttribute('data-parent') === this._config.parent + } + + return elem.classList.contains(ClassName.COLLAPSE) + }) + + if (actives.length === 0) { + actives = null + } + } + + if (actives) { + activesData = $(actives).not(this._selector).data(DATA_KEY) + if (activesData && activesData._isTransitioning) { + return + } + } + + const startEvent = $.Event(Event.SHOW) + $(this._element).trigger(startEvent) + if (startEvent.isDefaultPrevented()) { + return + } + + if (actives) { + Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide') + if (!activesData) { + $(actives).data(DATA_KEY, null) + } + } + + const dimension = this._getDimension() + + $(this._element) + .removeClass(ClassName.COLLAPSE) + .addClass(ClassName.COLLAPSING) + + this._element.style[dimension] = 0 + + if (this._triggerArray.length) { + $(this._triggerArray) + .removeClass(ClassName.COLLAPSED) + .attr('aria-expanded', true) + } + + this.setTransitioning(true) + + const complete = () => { + $(this._element) + .removeClass(ClassName.COLLAPSING) + .addClass(ClassName.COLLAPSE) + .addClass(ClassName.SHOW) + + this._element.style[dimension] = '' + + this.setTransitioning(false) + + $(this._element).trigger(Event.SHOWN) + } + + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) + const scrollSize = `scroll${capitalizedDimension}` + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + + $(this._element) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(transitionDuration) + + this._element.style[dimension] = `${this._element[scrollSize]}px` + } + + hide() { + if (this._isTransitioning || + !$(this._element).hasClass(ClassName.SHOW)) { + return + } + + const startEvent = $.Event(Event.HIDE) + $(this._element).trigger(startEvent) + if (startEvent.isDefaultPrevented()) { + return + } + + const dimension = this._getDimension() + + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px` + + Util.reflow(this._element) + + $(this._element) + .addClass(ClassName.COLLAPSING) + .removeClass(ClassName.COLLAPSE) + .removeClass(ClassName.SHOW) + + const triggerArrayLength = this._triggerArray.length + if (triggerArrayLength > 0) { + for (let i = 0; i < triggerArrayLength; i++) { + const trigger = this._triggerArray[i] + const selector = Util.getSelectorFromElement(trigger) + + if (selector !== null) { + const $elem = $([].slice.call(document.querySelectorAll(selector))) + if (!$elem.hasClass(ClassName.SHOW)) { + $(trigger).addClass(ClassName.COLLAPSED) + .attr('aria-expanded', false) + } + } + } + } + + this.setTransitioning(true) + + const complete = () => { + this.setTransitioning(false) + $(this._element) + .removeClass(ClassName.COLLAPSING) + .addClass(ClassName.COLLAPSE) + .trigger(Event.HIDDEN) + } + + this._element.style[dimension] = '' + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + + $(this._element) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(transitionDuration) + } + + setTransitioning(isTransitioning) { + this._isTransitioning = isTransitioning + } + + dispose() { + $.removeData(this._element, DATA_KEY) + + this._config = null + this._parent = null + this._element = null + this._triggerArray = null + this._isTransitioning = null + } + + // Private + + _getConfig(config) { + config = { + ...Default, + ...config + } + config.toggle = Boolean(config.toggle) // Coerce string values + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + + _getDimension() { + const hasWidth = $(this._element).hasClass(Dimension.WIDTH) + return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT + } + + _getParent() { + let parent + + if (Util.isElement(this._config.parent)) { + parent = this._config.parent + + // It's a jQuery object + if (typeof this._config.parent.jquery !== 'undefined') { + parent = this._config.parent[0] + } + } else { + parent = document.querySelector(this._config.parent) + } + + const selector = + `[data-toggle="collapse"][data-parent="${this._config.parent}"]` + + const children = [].slice.call(parent.querySelectorAll(selector)) + $(children).each((i, element) => { + this._addAriaAndCollapsedClass( + Collapse._getTargetFromElement(element), + [element] + ) + }) + + return parent + } + + _addAriaAndCollapsedClass(element, triggerArray) { + const isOpen = $(element).hasClass(ClassName.SHOW) + + if (triggerArray.length) { + $(triggerArray) + .toggleClass(ClassName.COLLAPSED, !isOpen) + .attr('aria-expanded', isOpen) + } + } + + // Static + + static _getTargetFromElement(element) { + const selector = Util.getSelectorFromElement(element) + return selector ? document.querySelector(selector) : null + } + + static _jQueryInterface(config) { + return this.each(function () { + const $this = $(this) + let data = $this.data(DATA_KEY) + const _config = { + ...Default, + ...$this.data(), + ...typeof config === 'object' && config ? config : {} + } + + if (!data && _config.toggle && /show|hide/.test(config)) { + _config.toggle = false + } + + if (!data) { + data = new Collapse(this, _config) + $this.data(DATA_KEY, data) + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + data[config]() + } + }) + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + // preventDefault only for elements (which change the URL) not inside the collapsible element + if (event.currentTarget.tagName === 'A') { + event.preventDefault() + } + + const $trigger = $(this) + const selector = Util.getSelectorFromElement(this) + const selectors = [].slice.call(document.querySelectorAll(selector)) + + $(selectors).each(function () { + const $target = $(this) + const data = $target.data(DATA_KEY) + const config = data ? 'toggle' : $trigger.data() + Collapse._jQueryInterface.call($target, config) + }) +}) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Collapse._jQueryInterface +$.fn[NAME].Constructor = Collapse +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Collapse._jQueryInterface +} + +export default Collapse diff --git a/app/static/node_modules/bootstrap/js/src/dropdown.js b/app/static/node_modules/bootstrap/js/src/dropdown.js new file mode 100644 index 0000000..d336a46 --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/dropdown.js @@ -0,0 +1,545 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Popper from 'popper.js' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'dropdown' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.dropdown' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] +const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key +const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key +const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key +const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key +const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key +const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) +const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`) + +const Event = { + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + CLICK : `click${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, + KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`, + KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + DISABLED : 'disabled', + SHOW : 'show', + DROPUP : 'dropup', + DROPRIGHT : 'dropright', + DROPLEFT : 'dropleft', + MENURIGHT : 'dropdown-menu-right', + MENULEFT : 'dropdown-menu-left', + POSITION_STATIC : 'position-static' +} + +const Selector = { + DATA_TOGGLE : '[data-toggle="dropdown"]', + FORM_CHILD : '.dropdown form', + MENU : '.dropdown-menu', + NAVBAR_NAV : '.navbar-nav', + VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' +} + +const AttachmentMap = { + TOP : 'top-start', + TOPEND : 'top-end', + BOTTOM : 'bottom-start', + BOTTOMEND : 'bottom-end', + RIGHT : 'right-start', + RIGHTEND : 'right-end', + LEFT : 'left-start', + LEFTEND : 'left-end' +} + +const Default = { + offset : 0, + flip : true, + boundary : 'scrollParent', + reference : 'toggle', + display : 'dynamic' +} + +const DefaultType = { + offset : '(number|string|function)', + flip : 'boolean', + boundary : '(string|element)', + reference : '(string|element)', + display : 'string' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Dropdown { + constructor(element, config) { + this._element = element + this._popper = null + this._config = this._getConfig(config) + this._menu = this._getMenuElement() + this._inNavbar = this._detectNavbar() + + this._addEventListeners() + } + + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + // Public + + toggle() { + if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED)) { + return + } + + const parent = Dropdown._getParentFromElement(this._element) + const isActive = $(this._menu).hasClass(ClassName.SHOW) + + Dropdown._clearMenus() + + if (isActive) { + return + } + + const relatedTarget = { + relatedTarget: this._element + } + const showEvent = $.Event(Event.SHOW, relatedTarget) + + $(parent).trigger(showEvent) + + if (showEvent.isDefaultPrevented()) { + return + } + + // Disable totally Popper.js for Dropdown in Navbar + if (!this._inNavbar) { + /** + * Check for Popper dependency + * Popper - https://popper.js.org + */ + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)') + } + + let referenceElement = this._element + + if (this._config.reference === 'parent') { + referenceElement = parent + } else if (Util.isElement(this._config.reference)) { + referenceElement = this._config.reference + + // Check if it's jQuery element + if (typeof this._config.reference.jquery !== 'undefined') { + referenceElement = this._config.reference[0] + } + } + + // If boundary is not `scrollParent`, then set position to `static` + // to allow the menu to "escape" the scroll parent's boundaries + // https://github.com/twbs/bootstrap/issues/24251 + if (this._config.boundary !== 'scrollParent') { + $(parent).addClass(ClassName.POSITION_STATIC) + } + this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()) + } + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement && + $(parent).closest(Selector.NAVBAR_NAV).length === 0) { + $(document.body).children().on('mouseover', null, $.noop) + } + + this._element.focus() + this._element.setAttribute('aria-expanded', true) + + $(this._menu).toggleClass(ClassName.SHOW) + $(parent) + .toggleClass(ClassName.SHOW) + .trigger($.Event(Event.SHOWN, relatedTarget)) + } + + show() { + if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || $(this._menu).hasClass(ClassName.SHOW)) { + return + } + + const relatedTarget = { + relatedTarget: this._element + } + const showEvent = $.Event(Event.SHOW, relatedTarget) + const parent = Dropdown._getParentFromElement(this._element) + + $(parent).trigger(showEvent) + + if (showEvent.isDefaultPrevented()) { + return + } + + $(this._menu).toggleClass(ClassName.SHOW) + $(parent) + .toggleClass(ClassName.SHOW) + .trigger($.Event(Event.SHOWN, relatedTarget)) + } + + hide() { + if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || !$(this._menu).hasClass(ClassName.SHOW)) { + return + } + + const relatedTarget = { + relatedTarget: this._element + } + const hideEvent = $.Event(Event.HIDE, relatedTarget) + const parent = Dropdown._getParentFromElement(this._element) + + $(parent).trigger(hideEvent) + + if (hideEvent.isDefaultPrevented()) { + return + } + + $(this._menu).toggleClass(ClassName.SHOW) + $(parent) + .toggleClass(ClassName.SHOW) + .trigger($.Event(Event.HIDDEN, relatedTarget)) + } + + dispose() { + $.removeData(this._element, DATA_KEY) + $(this._element).off(EVENT_KEY) + this._element = null + this._menu = null + if (this._popper !== null) { + this._popper.destroy() + this._popper = null + } + } + + update() { + this._inNavbar = this._detectNavbar() + if (this._popper !== null) { + this._popper.scheduleUpdate() + } + } + + // Private + + _addEventListeners() { + $(this._element).on(Event.CLICK, (event) => { + event.preventDefault() + event.stopPropagation() + this.toggle() + }) + } + + _getConfig(config) { + config = { + ...this.constructor.Default, + ...$(this._element).data(), + ...config + } + + Util.typeCheckConfig( + NAME, + config, + this.constructor.DefaultType + ) + + return config + } + + _getMenuElement() { + if (!this._menu) { + const parent = Dropdown._getParentFromElement(this._element) + + if (parent) { + this._menu = parent.querySelector(Selector.MENU) + } + } + return this._menu + } + + _getPlacement() { + const $parentDropdown = $(this._element.parentNode) + let placement = AttachmentMap.BOTTOM + + // Handle dropup + if ($parentDropdown.hasClass(ClassName.DROPUP)) { + placement = AttachmentMap.TOP + if ($(this._menu).hasClass(ClassName.MENURIGHT)) { + placement = AttachmentMap.TOPEND + } + } else if ($parentDropdown.hasClass(ClassName.DROPRIGHT)) { + placement = AttachmentMap.RIGHT + } else if ($parentDropdown.hasClass(ClassName.DROPLEFT)) { + placement = AttachmentMap.LEFT + } else if ($(this._menu).hasClass(ClassName.MENURIGHT)) { + placement = AttachmentMap.BOTTOMEND + } + return placement + } + + _detectNavbar() { + return $(this._element).closest('.navbar').length > 0 + } + + _getOffset() { + const offset = {} + + if (typeof this._config.offset === 'function') { + offset.fn = (data) => { + data.offsets = { + ...data.offsets, + ...this._config.offset(data.offsets, this._element) || {} + } + + return data + } + } else { + offset.offset = this._config.offset + } + + return offset + } + + _getPopperConfig() { + const popperConfig = { + placement: this._getPlacement(), + modifiers: { + offset: this._getOffset(), + flip: { + enabled: this._config.flip + }, + preventOverflow: { + boundariesElement: this._config.boundary + } + } + } + + // Disable Popper.js if we have a static display + if (this._config.display === 'static') { + popperConfig.modifiers.applyStyle = { + enabled: false + } + } + + return popperConfig + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + const _config = typeof config === 'object' ? config : null + + if (!data) { + data = new Dropdown(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + data[config]() + } + }) + } + + static _clearMenus(event) { + if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || + event.type === 'keyup' && event.which !== TAB_KEYCODE)) { + return + } + + const toggles = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE)) + + for (let i = 0, len = toggles.length; i < len; i++) { + const parent = Dropdown._getParentFromElement(toggles[i]) + const context = $(toggles[i]).data(DATA_KEY) + const relatedTarget = { + relatedTarget: toggles[i] + } + + if (event && event.type === 'click') { + relatedTarget.clickEvent = event + } + + if (!context) { + continue + } + + const dropdownMenu = context._menu + if (!$(parent).hasClass(ClassName.SHOW)) { + continue + } + + if (event && (event.type === 'click' && + /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && + $.contains(parent, event.target)) { + continue + } + + const hideEvent = $.Event(Event.HIDE, relatedTarget) + $(parent).trigger(hideEvent) + if (hideEvent.isDefaultPrevented()) { + continue + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + $(document.body).children().off('mouseover', null, $.noop) + } + + toggles[i].setAttribute('aria-expanded', 'false') + + $(dropdownMenu).removeClass(ClassName.SHOW) + $(parent) + .removeClass(ClassName.SHOW) + .trigger($.Event(Event.HIDDEN, relatedTarget)) + } + } + + static _getParentFromElement(element) { + let parent + const selector = Util.getSelectorFromElement(element) + + if (selector) { + parent = document.querySelector(selector) + } + + return parent || element.parentNode + } + + // eslint-disable-next-line complexity + static _dataApiKeydownHandler(event) { + // If not input/textarea: + // - And not a key in REGEXP_KEYDOWN => not a dropdown command + // If input/textarea: + // - If space key => not a dropdown command + // - If key is other than escape + // - If key is not up or down => not a dropdown command + // - If trigger inside the menu => not a dropdown command + if (/input|textarea/i.test(event.target.tagName) + ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && + (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || + $(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { + return + } + + event.preventDefault() + event.stopPropagation() + + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return + } + + const parent = Dropdown._getParentFromElement(this) + const isActive = $(parent).hasClass(ClassName.SHOW) + + if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { + if (event.which === ESCAPE_KEYCODE) { + const toggle = parent.querySelector(Selector.DATA_TOGGLE) + $(toggle).trigger('focus') + } + + $(this).trigger('click') + return + } + + const items = [].slice.call(parent.querySelectorAll(Selector.VISIBLE_ITEMS)) + + if (items.length === 0) { + return + } + + let index = items.indexOf(event.target) + + if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up + index-- + } + + if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down + index++ + } + + if (index < 0) { + index = 0 + } + + items[index].focus() + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document) + .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler) + .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus) + .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + event.preventDefault() + event.stopPropagation() + Dropdown._jQueryInterface.call($(this), 'toggle') + }) + .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => { + e.stopPropagation() + }) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Dropdown._jQueryInterface +$.fn[NAME].Constructor = Dropdown +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Dropdown._jQueryInterface +} + + +export default Dropdown diff --git a/app/static/node_modules/bootstrap/js/src/index.js b/app/static/node_modules/bootstrap/js/src/index.js new file mode 100644 index 0000000..c4a4d4b --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/index.js @@ -0,0 +1,52 @@ +import $ from 'jquery' +import Alert from './alert' +import Button from './button' +import Carousel from './carousel' +import Collapse from './collapse' +import Dropdown from './dropdown' +import Modal from './modal' +import Popover from './popover' +import Scrollspy from './scrollspy' +import Tab from './tab' +import Toast from './toast' +import Tooltip from './tooltip' +import Util from './util' + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): index.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +(() => { + if (typeof $ === 'undefined') { + throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.') + } + + const version = $.fn.jquery.split(' ')[0].split('.') + const minMajor = 1 + const ltMajor = 2 + const minMinor = 9 + const minPatch = 1 + const maxMajor = 4 + + if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) { + throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0') + } +})() + +export { + Util, + Alert, + Button, + Carousel, + Collapse, + Dropdown, + Modal, + Popover, + Scrollspy, + Tab, + Toast, + Tooltip +} diff --git a/app/static/node_modules/bootstrap/js/src/modal.js b/app/static/node_modules/bootstrap/js/src/modal.js new file mode 100644 index 0000000..99fe1bf --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/modal.js @@ -0,0 +1,594 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'modal' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.modal' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] +const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key + +const Default = { + backdrop : true, + keyboard : true, + focus : true, + show : true +} + +const DefaultType = { + backdrop : '(boolean|string)', + keyboard : 'boolean', + focus : 'boolean', + show : 'boolean' +} + +const Event = { + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + RESIZE : `resize${EVENT_KEY}`, + CLICK_DISMISS : `click.dismiss${EVENT_KEY}`, + KEYDOWN_DISMISS : `keydown.dismiss${EVENT_KEY}`, + MOUSEUP_DISMISS : `mouseup.dismiss${EVENT_KEY}`, + MOUSEDOWN_DISMISS : `mousedown.dismiss${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + SCROLLABLE : 'modal-dialog-scrollable', + SCROLLBAR_MEASURER : 'modal-scrollbar-measure', + BACKDROP : 'modal-backdrop', + OPEN : 'modal-open', + FADE : 'fade', + SHOW : 'show' +} + +const Selector = { + DIALOG : '.modal-dialog', + MODAL_BODY : '.modal-body', + DATA_TOGGLE : '[data-toggle="modal"]', + DATA_DISMISS : '[data-dismiss="modal"]', + FIXED_CONTENT : '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', + STICKY_CONTENT : '.sticky-top' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Modal { + constructor(element, config) { + this._config = this._getConfig(config) + this._element = element + this._dialog = element.querySelector(Selector.DIALOG) + this._backdrop = null + this._isShown = false + this._isBodyOverflowing = false + this._ignoreBackdropClick = false + this._isTransitioning = false + this._scrollbarWidth = 0 + } + + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + // Public + + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + if (this._isShown || this._isTransitioning) { + return + } + + if ($(this._element).hasClass(ClassName.FADE)) { + this._isTransitioning = true + } + + const showEvent = $.Event(Event.SHOW, { + relatedTarget + }) + + $(this._element).trigger(showEvent) + + if (this._isShown || showEvent.isDefaultPrevented()) { + return + } + + this._isShown = true + + this._checkScrollbar() + this._setScrollbar() + + this._adjustDialog() + + this._setEscapeEvent() + this._setResizeEvent() + + $(this._element).on( + Event.CLICK_DISMISS, + Selector.DATA_DISMISS, + (event) => this.hide(event) + ) + + $(this._dialog).on(Event.MOUSEDOWN_DISMISS, () => { + $(this._element).one(Event.MOUSEUP_DISMISS, (event) => { + if ($(event.target).is(this._element)) { + this._ignoreBackdropClick = true + } + }) + }) + + this._showBackdrop(() => this._showElement(relatedTarget)) + } + + hide(event) { + if (event) { + event.preventDefault() + } + + if (!this._isShown || this._isTransitioning) { + return + } + + const hideEvent = $.Event(Event.HIDE) + + $(this._element).trigger(hideEvent) + + if (!this._isShown || hideEvent.isDefaultPrevented()) { + return + } + + this._isShown = false + const transition = $(this._element).hasClass(ClassName.FADE) + + if (transition) { + this._isTransitioning = true + } + + this._setEscapeEvent() + this._setResizeEvent() + + $(document).off(Event.FOCUSIN) + + $(this._element).removeClass(ClassName.SHOW) + + $(this._element).off(Event.CLICK_DISMISS) + $(this._dialog).off(Event.MOUSEDOWN_DISMISS) + + + if (transition) { + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + + $(this._element) + .one(Util.TRANSITION_END, (event) => this._hideModal(event)) + .emulateTransitionEnd(transitionDuration) + } else { + this._hideModal() + } + } + + dispose() { + [window, this._element, this._dialog] + .forEach((htmlElement) => $(htmlElement).off(EVENT_KEY)) + + /** + * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` + * Do not move `document` in `htmlElements` array + * It will remove `Event.CLICK_DATA_API` event that should remain + */ + $(document).off(Event.FOCUSIN) + + $.removeData(this._element, DATA_KEY) + + this._config = null + this._element = null + this._dialog = null + this._backdrop = null + this._isShown = null + this._isBodyOverflowing = null + this._ignoreBackdropClick = null + this._isTransitioning = null + this._scrollbarWidth = null + } + + handleUpdate() { + this._adjustDialog() + } + + // Private + + _getConfig(config) { + config = { + ...Default, + ...config + } + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + + _showElement(relatedTarget) { + const transition = $(this._element).hasClass(ClassName.FADE) + + if (!this._element.parentNode || + this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { + // Don't move modal's DOM position + document.body.appendChild(this._element) + } + + this._element.style.display = 'block' + this._element.removeAttribute('aria-hidden') + this._element.setAttribute('aria-modal', true) + + if ($(this._dialog).hasClass(ClassName.SCROLLABLE)) { + this._dialog.querySelector(Selector.MODAL_BODY).scrollTop = 0 + } else { + this._element.scrollTop = 0 + } + + if (transition) { + Util.reflow(this._element) + } + + $(this._element).addClass(ClassName.SHOW) + + if (this._config.focus) { + this._enforceFocus() + } + + const shownEvent = $.Event(Event.SHOWN, { + relatedTarget + }) + + const transitionComplete = () => { + if (this._config.focus) { + this._element.focus() + } + this._isTransitioning = false + $(this._element).trigger(shownEvent) + } + + if (transition) { + const transitionDuration = Util.getTransitionDurationFromElement(this._dialog) + + $(this._dialog) + .one(Util.TRANSITION_END, transitionComplete) + .emulateTransitionEnd(transitionDuration) + } else { + transitionComplete() + } + } + + _enforceFocus() { + $(document) + .off(Event.FOCUSIN) // Guard against infinite focus loop + .on(Event.FOCUSIN, (event) => { + if (document !== event.target && + this._element !== event.target && + $(this._element).has(event.target).length === 0) { + this._element.focus() + } + }) + } + + _setEscapeEvent() { + if (this._isShown && this._config.keyboard) { + $(this._element).on(Event.KEYDOWN_DISMISS, (event) => { + if (event.which === ESCAPE_KEYCODE) { + event.preventDefault() + this.hide() + } + }) + } else if (!this._isShown) { + $(this._element).off(Event.KEYDOWN_DISMISS) + } + } + + _setResizeEvent() { + if (this._isShown) { + $(window).on(Event.RESIZE, (event) => this.handleUpdate(event)) + } else { + $(window).off(Event.RESIZE) + } + } + + _hideModal() { + this._element.style.display = 'none' + this._element.setAttribute('aria-hidden', true) + this._element.removeAttribute('aria-modal') + this._isTransitioning = false + this._showBackdrop(() => { + $(document.body).removeClass(ClassName.OPEN) + this._resetAdjustments() + this._resetScrollbar() + $(this._element).trigger(Event.HIDDEN) + }) + } + + _removeBackdrop() { + if (this._backdrop) { + $(this._backdrop).remove() + this._backdrop = null + } + } + + _showBackdrop(callback) { + const animate = $(this._element).hasClass(ClassName.FADE) + ? ClassName.FADE : '' + + if (this._isShown && this._config.backdrop) { + this._backdrop = document.createElement('div') + this._backdrop.className = ClassName.BACKDROP + + if (animate) { + this._backdrop.classList.add(animate) + } + + $(this._backdrop).appendTo(document.body) + + $(this._element).on(Event.CLICK_DISMISS, (event) => { + if (this._ignoreBackdropClick) { + this._ignoreBackdropClick = false + return + } + if (event.target !== event.currentTarget) { + return + } + if (this._config.backdrop === 'static') { + this._element.focus() + } else { + this.hide() + } + }) + + if (animate) { + Util.reflow(this._backdrop) + } + + $(this._backdrop).addClass(ClassName.SHOW) + + if (!callback) { + return + } + + if (!animate) { + callback() + return + } + + const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + + $(this._backdrop) + .one(Util.TRANSITION_END, callback) + .emulateTransitionEnd(backdropTransitionDuration) + } else if (!this._isShown && this._backdrop) { + $(this._backdrop).removeClass(ClassName.SHOW) + + const callbackRemove = () => { + this._removeBackdrop() + if (callback) { + callback() + } + } + + if ($(this._element).hasClass(ClassName.FADE)) { + const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + + $(this._backdrop) + .one(Util.TRANSITION_END, callbackRemove) + .emulateTransitionEnd(backdropTransitionDuration) + } else { + callbackRemove() + } + } else if (callback) { + callback() + } + } + + // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // todo (fat): these should probably be refactored out of modal.js + // ---------------------------------------------------------------------- + + _adjustDialog() { + const isModalOverflowing = + this._element.scrollHeight > document.documentElement.clientHeight + + if (!this._isBodyOverflowing && isModalOverflowing) { + this._element.style.paddingLeft = `${this._scrollbarWidth}px` + } + + if (this._isBodyOverflowing && !isModalOverflowing) { + this._element.style.paddingRight = `${this._scrollbarWidth}px` + } + } + + _resetAdjustments() { + this._element.style.paddingLeft = '' + this._element.style.paddingRight = '' + } + + _checkScrollbar() { + const rect = document.body.getBoundingClientRect() + this._isBodyOverflowing = rect.left + rect.right < window.innerWidth + this._scrollbarWidth = this._getScrollbarWidth() + } + + _setScrollbar() { + if (this._isBodyOverflowing) { + // Note: DOMNode.style.paddingRight returns the actual value or '' if not set + // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set + const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)) + const stickyContent = [].slice.call(document.querySelectorAll(Selector.STICKY_CONTENT)) + + // Adjust fixed content padding + $(fixedContent).each((index, element) => { + const actualPadding = element.style.paddingRight + const calculatedPadding = $(element).css('padding-right') + $(element) + .data('padding-right', actualPadding) + .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) + }) + + // Adjust sticky content margin + $(stickyContent).each((index, element) => { + const actualMargin = element.style.marginRight + const calculatedMargin = $(element).css('margin-right') + $(element) + .data('margin-right', actualMargin) + .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`) + }) + + // Adjust body padding + const actualPadding = document.body.style.paddingRight + const calculatedPadding = $(document.body).css('padding-right') + $(document.body) + .data('padding-right', actualPadding) + .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) + } + + $(document.body).addClass(ClassName.OPEN) + } + + _resetScrollbar() { + // Restore fixed content padding + const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)) + $(fixedContent).each((index, element) => { + const padding = $(element).data('padding-right') + $(element).removeData('padding-right') + element.style.paddingRight = padding ? padding : '' + }) + + // Restore sticky content + const elements = [].slice.call(document.querySelectorAll(`${Selector.STICKY_CONTENT}`)) + $(elements).each((index, element) => { + const margin = $(element).data('margin-right') + if (typeof margin !== 'undefined') { + $(element).css('margin-right', margin).removeData('margin-right') + } + }) + + // Restore body padding + const padding = $(document.body).data('padding-right') + $(document.body).removeData('padding-right') + document.body.style.paddingRight = padding ? padding : '' + } + + _getScrollbarWidth() { // thx d.walsh + const scrollDiv = document.createElement('div') + scrollDiv.className = ClassName.SCROLLBAR_MEASURER + document.body.appendChild(scrollDiv) + const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth + document.body.removeChild(scrollDiv) + return scrollbarWidth + } + + // Static + + static _jQueryInterface(config, relatedTarget) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + const _config = { + ...Default, + ...$(this).data(), + ...typeof config === 'object' && config ? config : {} + } + + if (!data) { + data = new Modal(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + data[config](relatedTarget) + } else if (_config.show) { + data.show(relatedTarget) + } + }) + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + let target + const selector = Util.getSelectorFromElement(this) + + if (selector) { + target = document.querySelector(selector) + } + + const config = $(target).data(DATA_KEY) + ? 'toggle' : { + ...$(target).data(), + ...$(this).data() + } + + if (this.tagName === 'A' || this.tagName === 'AREA') { + event.preventDefault() + } + + const $target = $(target).one(Event.SHOW, (showEvent) => { + if (showEvent.isDefaultPrevented()) { + // Only register focus restorer if modal will actually get shown + return + } + + $target.one(Event.HIDDEN, () => { + if ($(this).is(':visible')) { + this.focus() + } + }) + }) + + Modal._jQueryInterface.call($(target), config, this) +}) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Modal._jQueryInterface +$.fn[NAME].Constructor = Modal +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Modal._jQueryInterface +} + +export default Modal diff --git a/app/static/node_modules/bootstrap/js/src/popover.js b/app/static/node_modules/bootstrap/js/src/popover.js new file mode 100644 index 0000000..98f2f3f --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/popover.js @@ -0,0 +1,184 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Tooltip from './tooltip' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'popover' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.popover' +const EVENT_KEY = `.${DATA_KEY}` +const JQUERY_NO_CONFLICT = $.fn[NAME] +const CLASS_PREFIX = 'bs-popover' +const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g') + +const Default = { + ...Tooltip.Default, + placement : 'right', + trigger : 'click', + content : '', + template : '' +} + +const DefaultType = { + ...Tooltip.DefaultType, + content : '(string|element|function)' +} + +const ClassName = { + FADE : 'fade', + SHOW : 'show' +} + +const Selector = { + TITLE : '.popover-header', + CONTENT : '.popover-body' +} + +const Event = { + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}`, + INSERTED : `inserted${EVENT_KEY}`, + CLICK : `click${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + FOCUSOUT : `focusout${EVENT_KEY}`, + MOUSEENTER : `mouseenter${EVENT_KEY}`, + MOUSELEAVE : `mouseleave${EVENT_KEY}` +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Popover extends Tooltip { + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + static get NAME() { + return NAME + } + + static get DATA_KEY() { + return DATA_KEY + } + + static get Event() { + return Event + } + + static get EVENT_KEY() { + return EVENT_KEY + } + + static get DefaultType() { + return DefaultType + } + + // Overrides + + isWithContent() { + return this.getTitle() || this._getContent() + } + + addAttachmentClass(attachment) { + $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`) + } + + getTipElement() { + this.tip = this.tip || $(this.config.template)[0] + return this.tip + } + + setContent() { + const $tip = $(this.getTipElement()) + + // We use append for html objects to maintain js events + this.setElementContent($tip.find(Selector.TITLE), this.getTitle()) + let content = this._getContent() + if (typeof content === 'function') { + content = content.call(this.element) + } + this.setElementContent($tip.find(Selector.CONTENT), content) + + $tip.removeClass(`${ClassName.FADE} ${ClassName.SHOW}`) + } + + // Private + + _getContent() { + return this.element.getAttribute('data-content') || + this.config.content + } + + _cleanTipClass() { + const $tip = $(this.getTipElement()) + const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX) + if (tabClass !== null && tabClass.length > 0) { + $tip.removeClass(tabClass.join('')) + } + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + const _config = typeof config === 'object' ? config : null + + if (!data && /dispose|hide/.test(config)) { + return + } + + if (!data) { + data = new Popover(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + data[config]() + } + }) + } +} + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +$.fn[NAME] = Popover._jQueryInterface +$.fn[NAME].Constructor = Popover +$.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Popover._jQueryInterface +} + +export default Popover diff --git a/app/static/node_modules/bootstrap/js/src/scrollspy.js b/app/static/node_modules/bootstrap/js/src/scrollspy.js new file mode 100644 index 0000000..e8cd6bf --- /dev/null +++ b/app/static/node_modules/bootstrap/js/src/scrollspy.js @@ -0,0 +1,326 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import $ from 'jquery' +import Util from './util' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'scrollspy' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.scrollspy' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const JQUERY_NO_CONFLICT = $.fn[NAME] + +const Default = { + offset : 10, + method : 'auto', + target : '' +} + +const DefaultType = { + offset : 'number', + method : 'string', + target : '(string|element)' +} + +const Event = { + ACTIVATE : `activate${EVENT_KEY}`, + SCROLL : `scroll${EVENT_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + DROPDOWN_ITEM : 'dropdown-item', + DROPDOWN_MENU : 'dropdown-menu', + ACTIVE : 'active' +} + +const Selector = { + DATA_SPY : '[data-spy="scroll"]', + ACTIVE : '.active', + NAV_LIST_GROUP : '.nav, .list-group', + NAV_LINKS : '.nav-link', + NAV_ITEMS : '.nav-item', + LIST_ITEMS : '.list-group-item', + DROPDOWN : '.dropdown', + DROPDOWN_ITEMS : '.dropdown-item', + DROPDOWN_TOGGLE : '.dropdown-toggle' +} + +const OffsetMethod = { + OFFSET : 'offset', + POSITION : 'position' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class ScrollSpy { + constructor(element, config) { + this._element = element + this._scrollElement = element.tagName === 'BODY' ? window : element + this._config = this._getConfig(config) + this._selector = `${this._config.target} ${Selector.NAV_LINKS},` + + `${this._config.target} ${Selector.LIST_ITEMS},` + + `${this._config.target} ${Selector.DROPDOWN_ITEMS}` + this._offsets = [] + this._targets = [] + this._activeTarget = null + this._scrollHeight = 0 + + $(this._scrollElement).on(Event.SCROLL, (event) => this._process(event)) + + this.refresh() + this._process() + } + + // Getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + // Public + + refresh() { + const autoMethod = this._scrollElement === this._scrollElement.window + ? OffsetMethod.OFFSET : OffsetMethod.POSITION + + const offsetMethod = this._config.method === 'auto' + ? autoMethod : this._config.method + + const offsetBase = offsetMethod === OffsetMethod.POSITION + ? this._getScrollTop() : 0 + + this._offsets = [] + this._targets = [] + + this._scrollHeight = this._getScrollHeight() + + const targets = [].slice.call(document.querySelectorAll(this._selector)) + + targets + .map((element) => { + let target + const targetSelector = Util.getSelectorFromElement(element) + + if (targetSelector) { + target = document.querySelector(targetSelector) + } + + if (target) { + const targetBCR = target.getBoundingClientRect() + if (targetBCR.width || targetBCR.height) { + // TODO (fat): remove sketch reliance on jQuery position/offset + return [ + $(target)[offsetMethod]().top + offsetBase, + targetSelector + ] + } + } + return null + }) + .filter((item) => item) + .sort((a, b) => a[0] - b[0]) + .forEach((item) => { + this._offsets.push(item[0]) + this._targets.push(item[1]) + }) + } + + dispose() { + $.removeData(this._element, DATA_KEY) + $(this._scrollElement).off(EVENT_KEY) + + this._element = null + this._scrollElement = null + this._config = null + this._selector = null + this._offsets = null + this._targets = null + this._activeTarget = null + this._scrollHeight = null + } + + // Private + + _getConfig(config) { + config = { + ...Default, + ...typeof config === 'object' && config ? config : {} + } + + if (typeof config.target !== 'string') { + let id = $(config.target).attr('id') + if (!id) { + id = Util.getUID(NAME) + $(config.target).attr('id', id) + } + config.target = `#${id}` + } + + Util.typeCheckConfig(NAME, config, DefaultType) + + return config + } + + _getScrollTop() { + return this._scrollElement === window + ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop + } + + _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max( + document.body.scrollHeight, + document.documentElement.scrollHeight + ) + } + + _getOffsetHeight() { + return this._scrollElement === window + ? window.innerHeight : this._scrollElement.getBoundingClientRect().height + } + + _process() { + const scrollTop = this._getScrollTop() + this._config.offset + const scrollHeight = this._getScrollHeight() + const maxScroll = this._config.offset + + scrollHeight - + this._getOffsetHeight() + + if (this._scrollHeight !== scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + const target = this._targets[this._targets.length - 1] + + if (this._activeTarget !== target) { + this._activate(target) + } + return + } + + if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { + this._activeTarget = null + this._clear() + return + } + + const offsetLength = this._offsets.length + for (let i = offsetLength; i--;) { + const isActiveTarget = this._activeTarget !== this._targets[i] && + scrollTop >= this._offsets[i] && + (typeof this._offsets[i + 1] === 'undefined' || + scrollTop < this._offsets[i + 1]) + + if (isActiveTarget) { + this._activate(this._targets[i]) + } + } + } + + _activate(target) { + this._activeTarget = target + + this._clear() + + const queries = this._selector + .split(',') + .map((selector) => `${selector}[data-target="${target}"],${selector}[href="${target}"]`) + + const $link = $([].slice.call(document.querySelectorAll(queries.join(',')))) + + if ($link.hasClass(ClassName.DROPDOWN_ITEM)) { + $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE) + $link.addClass(ClassName.ACTIVE) + } else { + // Set triggered link as active + $link.addClass(ClassName.ACTIVE) + // Set triggered links parents as active + // With both